@mcptoolshop/housekeeping 0.0.0 → 1.3.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.
- package/CHANGELOG.md +103 -0
- package/LICENSE +21 -0
- package/README.es.md +217 -0
- package/README.fr.md +195 -0
- package/README.hi.md +195 -0
- package/README.it.md +195 -0
- package/README.ja.md +199 -0
- package/README.md +246 -5
- package/README.pt-BR.md +222 -0
- package/README.zh.md +197 -0
- package/housekeeping.config.example.json +4 -0
- package/package.json +55 -5
- package/rules/atlas-map.md +30 -0
- package/rules/github-actions.md +59 -0
- package/rules/repo-first.md +12 -0
- package/rules/shipcheck-product-standards.md +37 -0
- package/src/analyze.mjs +1360 -0
- package/src/cli.mjs +316 -0
- package/src/collect.mjs +1184 -0
- package/src/config.mjs +75 -0
- package/src/cost.mjs +271 -0
- package/src/errors.mjs +102 -0
- package/src/gh.mjs +286 -0
- package/src/load.mjs +907 -0
- package/src/lockfile.mjs +272 -0
- package/src/log.mjs +41 -0
- package/src/mcp.mjs +298 -0
- package/src/paths.mjs +31 -0
- package/src/report.mjs +570 -0
- package/src/schema.sql +385 -0
- package/src/workflow-risks.mjs +455 -0
package/src/schema.sql
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
-- housekeeping: org operational-health warehouse
|
|
2
|
+
-- Snapshot-oriented: every collection run is an immutable row set, so drift is a JOIN.
|
|
3
|
+
PRAGMA journal_mode = WAL;
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS snapshot (
|
|
6
|
+
id INTEGER PRIMARY KEY,
|
|
7
|
+
taken_at TEXT NOT NULL,
|
|
8
|
+
org TEXT NOT NULL,
|
|
9
|
+
collector_version TEXT,
|
|
10
|
+
repo_count INTEGER,
|
|
11
|
+
duration_ms INTEGER,
|
|
12
|
+
gh_login TEXT,
|
|
13
|
+
notes TEXT
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS repo (
|
|
17
|
+
snapshot_id INTEGER NOT NULL,
|
|
18
|
+
name TEXT NOT NULL,
|
|
19
|
+
node_id TEXT,
|
|
20
|
+
description TEXT,
|
|
21
|
+
url TEXT,
|
|
22
|
+
homepage TEXT,
|
|
23
|
+
is_private INTEGER, is_archived INTEGER, is_fork INTEGER,
|
|
24
|
+
is_template INTEGER, is_empty INTEGER,
|
|
25
|
+
created_at TEXT, updated_at TEXT, pushed_at TEXT,
|
|
26
|
+
days_since_push INTEGER,
|
|
27
|
+
disk_usage_kb INTEGER, stars INTEGER, forks INTEGER, watchers INTEGER,
|
|
28
|
+
primary_language TEXT, license TEXT,
|
|
29
|
+
has_issues INTEGER, has_wiki INTEGER, has_discussions INTEGER, has_projects INTEGER,
|
|
30
|
+
default_branch TEXT, head_oid TEXT, head_committed_at TEXT,
|
|
31
|
+
ci_rollup TEXT, -- SUCCESS | FAILURE | PENDING | ERROR | EXPECTED | NONE
|
|
32
|
+
open_issues INTEGER, closed_issues INTEGER,
|
|
33
|
+
open_prs INTEGER, merged_prs INTEGER, closed_prs INTEGER,
|
|
34
|
+
release_count INTEGER, latest_release_tag TEXT, latest_release_at TEXT,
|
|
35
|
+
tag_count INTEGER, latest_tag TEXT, latest_tag_at TEXT,
|
|
36
|
+
pkg_name TEXT, pkg_version TEXT, pkg_private INTEGER,
|
|
37
|
+
npm_latest_version TEXT, npm_published_at TEXT, npm_status TEXT,
|
|
38
|
+
topic_count INTEGER, workflow_count INTEGER, translation_count INTEGER,
|
|
39
|
+
is_monorepo INTEGER,
|
|
40
|
+
-- Handbook site sidebar, stored as the two halves of one compatibility fact.
|
|
41
|
+
-- Neither column is a defect on its own; see HANDBOOK_SIDEBAR_* in analyze.mjs.
|
|
42
|
+
-- NULL means "no site/astro.config.mjs", or a snapshot taken before this was
|
|
43
|
+
-- collected -- both of which the rules read as "not measured", not "clean".
|
|
44
|
+
site_sidebar_shape TEXT, -- labeled-autogenerate | items-autogenerate | explicit | none
|
|
45
|
+
site_starlight_range TEXT, -- the declared range, verbatim (e.g. ^0.37.6)
|
|
46
|
+
-- 1 = atlas/structure.json is on the default branch, 0 = it is not, NULL =
|
|
47
|
+
-- not collected (a snapshot before collector 1.3.0). See rules/atlas-map.md.
|
|
48
|
+
has_atlas_map INTEGER,
|
|
49
|
+
PRIMARY KEY (snapshot_id, name)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- The committed Atlas map, one row per repo whose map the sweep saw. `error`
|
|
53
|
+
-- set means the map exists but could not be read: it is present, and nothing
|
|
54
|
+
-- is known about what it says. `engine` NULL on a readable map means it was
|
|
55
|
+
-- made before Atlas 1.23.0, which did not record one.
|
|
56
|
+
CREATE TABLE IF NOT EXISTS atlas_map (
|
|
57
|
+
snapshot_id INTEGER, repo TEXT, blob_sha TEXT, size_bytes INTEGER,
|
|
58
|
+
engine TEXT, generated_from TEXT, door_count INTEGER, error TEXT
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
-- Each door of a map, trimmed (collect.mjs trimAtlasDoor). The JSON columns
|
|
62
|
+
-- are kept as Atlas wrote them; `jobs`, `findings` and `unresolved_checks`
|
|
63
|
+
-- are NULL until the map's engine records them (Atlas 1.24.0).
|
|
64
|
+
CREATE TABLE IF NOT EXISTS atlas_door (
|
|
65
|
+
snapshot_id INTEGER, repo TEXT, file TEXT, name TEXT, kind TEXT,
|
|
66
|
+
triggers TEXT, sends TEXT, counts TEXT, jobs TEXT, findings TEXT,
|
|
67
|
+
unresolved_checks TEXT
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
-- Each command of a door: the job and step it runs in and the programs it
|
|
71
|
+
-- runs. `step` is Atlas's step reference as recorded in the map: the step's
|
|
72
|
+
-- name, or its index from 0 as a string when it has none.
|
|
73
|
+
CREATE TABLE IF NOT EXISTS atlas_door_command (
|
|
74
|
+
snapshot_id INTEGER, repo TEXT, file TEXT, door TEXT,
|
|
75
|
+
job TEXT, step TEXT, programs TEXT, directory TEXT
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
-- The engine version the fleet is measured against, once per snapshot.
|
|
79
|
+
CREATE TABLE IF NOT EXISTS atlas_fleet (
|
|
80
|
+
snapshot_id INTEGER, version TEXT, source TEXT, error TEXT
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS topic (
|
|
84
|
+
snapshot_id INTEGER, repo TEXT, topic TEXT
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
-- package.json scripts and devDependencies, so a rule can ask whether a
|
|
88
|
+
-- declared tool is actually invoked anywhere. A checker nobody runs is a
|
|
89
|
+
-- gate that exists on paper only.
|
|
90
|
+
CREATE TABLE IF NOT EXISTS package_script (
|
|
91
|
+
snapshot_id INTEGER, repo TEXT, name TEXT, command TEXT
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS package_dep (
|
|
95
|
+
snapshot_id INTEGER, repo TEXT, name TEXT, spec TEXT, kind TEXT -- prod | dev
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
CREATE TABLE IF NOT EXISTS language (
|
|
99
|
+
snapshot_id INTEGER, repo TEXT, language TEXT, bytes INTEGER, pct REAL
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
CREATE TABLE IF NOT EXISTS issue (
|
|
103
|
+
snapshot_id INTEGER, repo TEXT, number INTEGER, title TEXT,
|
|
104
|
+
author TEXT, created_at TEXT, updated_at TEXT, age_days INTEGER,
|
|
105
|
+
stale_days INTEGER, comments INTEGER, labels TEXT, url TEXT
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
CREATE TABLE IF NOT EXISTS pull_request (
|
|
109
|
+
snapshot_id INTEGER, repo TEXT, number INTEGER, title TEXT,
|
|
110
|
+
author TEXT, created_at TEXT, updated_at TEXT, age_days INTEGER,
|
|
111
|
+
stale_days INTEGER, is_draft INTEGER, mergeable TEXT, review_decision TEXT,
|
|
112
|
+
additions INTEGER, deletions INTEGER, changed_files INTEGER,
|
|
113
|
+
head_ref TEXT, base_ref TEXT, labels TEXT, url TEXT
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
CREATE TABLE IF NOT EXISTS release (
|
|
117
|
+
snapshot_id INTEGER, repo TEXT, tag TEXT, name TEXT,
|
|
118
|
+
published_at TEXT, is_latest INTEGER, is_draft INTEGER, is_prerelease INTEGER
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
-- git tags are NOT GitHub Releases: a repo can be correctly tagged and simply
|
|
122
|
+
-- never have had a Release published. Conflating them produced false drift.
|
|
123
|
+
CREATE TABLE IF NOT EXISTS tag (
|
|
124
|
+
snapshot_id INTEGER, repo TEXT, name TEXT, tagged_at TEXT,
|
|
125
|
+
is_semver INTEGER, has_release INTEGER
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
CREATE TABLE IF NOT EXISTS workflow (
|
|
129
|
+
snapshot_id INTEGER, repo TEXT, path TEXT, name TEXT, state TEXT,
|
|
130
|
+
-- mechanical checks against rules/github-actions.md
|
|
131
|
+
has_paths_filter INTEGER, has_workflow_dispatch INTEGER, has_concurrency INTEGER,
|
|
132
|
+
runners TEXT, uses_macos INTEGER, uses_windows INTEGER,
|
|
133
|
+
on_triggers TEXT, job_count INTEGER, has_matrix INTEGER, size_bytes INTEGER,
|
|
134
|
+
-- statically decidable failures that only surface on a schedule
|
|
135
|
+
edits_workflow_files INTEGER, pr_create_default_token INTEGER,
|
|
136
|
+
-- Job ids plus literal `name:` values, newline-joined. Names containing a
|
|
137
|
+
-- ${{ }} expression are omitted: what GitHub finally calls that check cannot
|
|
138
|
+
-- be known from the file. Used to tell a required context whose job was
|
|
139
|
+
-- deleted from one whose job is alive but simply did not run here.
|
|
140
|
+
job_names TEXT,
|
|
141
|
+
-- Dependency scanners split by whether they can actually fail the job. A
|
|
142
|
+
-- scanner ending in `|| true`, or carrying continue-on-error, renders exactly
|
|
143
|
+
-- the same green check as a clean one -- so counting audit steps without
|
|
144
|
+
-- asking whether they can fail measures ceremony, not coverage.
|
|
145
|
+
audit_steps_enforcing INTEGER, audit_steps_defanged TEXT,
|
|
146
|
+
-- Job ids with a step that uses actions/deploy-pages, newline-joined.
|
|
147
|
+
pages_deploy_jobs TEXT,
|
|
148
|
+
-- The pin of each `@dogfood-lab/atlas@<version> check` a run step issues,
|
|
149
|
+
-- newline-joined; `unpinned` for a call without a version. Empty: none.
|
|
150
|
+
atlas_check TEXT
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
-- One row per job that names a deployment environment, read from the YAML at
|
|
154
|
+
-- load. `runs_on_default` is 1 when a push to the default branch, a schedule
|
|
155
|
+
-- or a workflow_run can start the job, 0 when none can (a tag-only release),
|
|
156
|
+
-- NULL when the triggers or the job's `if:` are beyond what is evaluated. A
|
|
157
|
+
-- name written as an expression is not stored at all. `environment` is the
|
|
158
|
+
-- name the repository's Atlas map records for the job when it records one
|
|
159
|
+
-- (`source` = 'atlas'), else the workflow's own (`source` = 'workflow');
|
|
160
|
+
-- `parsed` is always the workflow's own reading, so the two can be compared.
|
|
161
|
+
CREATE TABLE IF NOT EXISTS workflow_environment (
|
|
162
|
+
snapshot_id INTEGER, repo TEXT, path TEXT, job TEXT,
|
|
163
|
+
environment TEXT, runs_on_default INTEGER, source TEXT, parsed TEXT
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
-- The repository settings a deploy depends on, asked only of repos whose
|
|
167
|
+
-- workflows need them (see collectDeploySettings). No row: not asked, or the
|
|
168
|
+
-- snapshot predates the pass. Every NULL below means unknown -- a 403, a
|
|
169
|
+
-- gateway error, a question that was not asked -- and never "off".
|
|
170
|
+
CREATE TABLE IF NOT EXISTS deploy_settings (
|
|
171
|
+
snapshot_id INTEGER, repo TEXT,
|
|
172
|
+
pages_status INTEGER, -- HTTP status of repos/{r}/pages; NULL = not asked
|
|
173
|
+
pages_enabled INTEGER, -- 1 on (200), 0 off (404), NULL unknown
|
|
174
|
+
pages_build_type TEXT, -- workflow | legacy, when on
|
|
175
|
+
pages_source_branch TEXT,
|
|
176
|
+
environments_status INTEGER, -- HTTP status of repos/{r}/environments; NULL = not asked
|
|
177
|
+
default_branch TEXT,
|
|
178
|
+
default_protected INTEGER, -- asked only for a "protected branches" environment
|
|
179
|
+
any_branch_protected INTEGER,
|
|
180
|
+
default_ruleset_rules INTEGER
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
-- One row per environment the repo has, from the same call. `policy` is
|
|
184
|
+
-- GitHub's deployment branch policy: 'all' (no restriction), 'protected'
|
|
185
|
+
-- (protected branches only) or 'custom' (named patterns). `branch_policies`
|
|
186
|
+
-- holds the custom patterns as `type:name`, newline-joined, and is read only
|
|
187
|
+
-- for environments a workflow names; `policies_readable` is NULL when they
|
|
188
|
+
-- were not asked for, 0 when the call did not answer 200 or was cut short.
|
|
189
|
+
CREATE TABLE IF NOT EXISTS environment (
|
|
190
|
+
snapshot_id INTEGER, repo TEXT, name TEXT,
|
|
191
|
+
policy TEXT, branch_policies TEXT, policies_readable INTEGER
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
CREATE TABLE IF NOT EXISTS workflow_run (
|
|
195
|
+
snapshot_id INTEGER, repo TEXT, workflow_name TEXT, path TEXT,
|
|
196
|
+
run_id INTEGER, run_number INTEGER, event TEXT, status TEXT, conclusion TEXT,
|
|
197
|
+
branch TEXT, created_at TEXT, updated_at TEXT, duration_s INTEGER, url TEXT,
|
|
198
|
+
is_latest_for_workflow INTEGER, -- newest run for this workflow on any branch
|
|
199
|
+
on_default_branch INTEGER, -- run's head branch == repo default branch
|
|
200
|
+
is_latest_on_default INTEGER -- newest run for this workflow ON the default branch
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
-- Where a red default-branch run broke (collect.mjs collectFailedSteps), one
|
|
204
|
+
-- row per failed step of a failed job, or one row with step NULL for a job
|
|
205
|
+
-- that failed outside any step. `api_job` and `api_step` are as the Actions
|
|
206
|
+
-- API displays them; `job` and `step` are the same place as an Atlas map
|
|
207
|
+
-- names it (workflow-risks.mjs resolveFailedStep), so they join
|
|
208
|
+
-- atlas_door_command on (repo, file = path, job, step). `unresolved` says why
|
|
209
|
+
-- no single step of the file could be named; then `step` is NULL. The file is
|
|
210
|
+
-- read at the sweep, the run may be older: a workflow edited since is one of
|
|
211
|
+
-- the reasons. No rows for a run means it was not read, never that it passed.
|
|
212
|
+
CREATE TABLE IF NOT EXISTS run_failed_step (
|
|
213
|
+
snapshot_id INTEGER, repo TEXT, run_id INTEGER, path TEXT,
|
|
214
|
+
api_job TEXT, api_step TEXT, step_number INTEGER,
|
|
215
|
+
job TEXT, step TEXT, phase TEXT, unresolved TEXT
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
CREATE TABLE IF NOT EXISTS file_presence (
|
|
219
|
+
snapshot_id INTEGER, repo TEXT,
|
|
220
|
+
readme INTEGER, license INTEGER, changelog INTEGER, security INTEGER,
|
|
221
|
+
contributing INTEGER, code_of_conduct INTEGER, codeowners INTEGER,
|
|
222
|
+
dependabot INTEGER, gitignore INTEGER, package_json INTEGER,
|
|
223
|
+
ship_gate INTEGER, claude_md INTEGER, workflows_dir INTEGER,
|
|
224
|
+
root_entries INTEGER
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
-- One row per OPEN Dependabot alert. Vulnerability exposure is not derivable
|
|
228
|
+
-- from anything else the collector gathers: a repo can be green, released and
|
|
229
|
+
-- fully documented while carrying critical advisories.
|
|
230
|
+
CREATE TABLE IF NOT EXISTS security_alert (
|
|
231
|
+
snapshot_id INTEGER, repo TEXT, number INTEGER,
|
|
232
|
+
severity TEXT, ecosystem TEXT, package TEXT, manifest TEXT,
|
|
233
|
+
ghsa TEXT, summary TEXT, created_at TEXT, age_days INTEGER,
|
|
234
|
+
fixed_version TEXT, -- NULL when no patched release exists yet
|
|
235
|
+
url TEXT
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
-- Per-repo security posture. Kept apart from `repo` so the alert sweep can be
|
|
239
|
+
-- absent (missing scope, API outage) without corrupting the main row.
|
|
240
|
+
CREATE TABLE IF NOT EXISTS repo_security (
|
|
241
|
+
snapshot_id INTEGER, repo TEXT,
|
|
242
|
+
vuln_alerts INTEGER, -- 1 scanned, 0 NOT scanned, NULL unreadable
|
|
243
|
+
auto_security_fixes INTEGER, -- 1 on, 0 off, NULL not readable
|
|
244
|
+
alerts_total INTEGER, critical INTEGER, high INTEGER, medium INTEGER, low INTEGER,
|
|
245
|
+
oldest_at TEXT, oldest_age_days INTEGER, fixable INTEGER
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
-- One row per committed npm lockfile, audited by the warehouse itself against
|
|
249
|
+
-- the npm advisory registry. This exists because GitHub's alert coverage is
|
|
250
|
+
-- silently per-manifest: one repo reported alerts on its root lockfile and 0
|
|
251
|
+
-- on site/package-lock.json, another reported 0 on both, and npm audit found a
|
|
252
|
+
-- critical in each of the uncounted ones. The SBOM endpoint's 404 is NOT the
|
|
253
|
+
-- signal (a repo can answer 404 while counting), and pushes do not repopulate
|
|
254
|
+
-- the graph. So the counter is read
|
|
255
|
+
-- directly. `github_alerts` is the open Dependabot alerts whose manifest_path
|
|
256
|
+
-- equals `path`, joined at load time; zero there against a non-zero
|
|
257
|
+
-- critical+high here is the false negative made measurable.
|
|
258
|
+
CREATE TABLE IF NOT EXISTS lockfile (
|
|
259
|
+
snapshot_id INTEGER, repo TEXT, path TEXT, blob_sha TEXT, size_bytes INTEGER,
|
|
260
|
+
lockfile_version INTEGER, packages INTEGER,
|
|
261
|
+
critical INTEGER, high INTEGER, medium INTEGER, low INTEGER,
|
|
262
|
+
-- The subset that SHIPS: advisories on packages with at least one non-dev
|
|
263
|
+
-- edge. A critical in vitest is real but is not what users install, and
|
|
264
|
+
-- many audit gates run --omit=dev, so the two must not be collapsed.
|
|
265
|
+
prod_critical INTEGER, prod_high INTEGER,
|
|
266
|
+
github_alerts INTEGER,
|
|
267
|
+
error TEXT -- non-NULL means NOT measured; never read as clean
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
CREATE TABLE IF NOT EXISTS lockfile_advisory (
|
|
271
|
+
snapshot_id INTEGER, repo TEXT, path TEXT,
|
|
272
|
+
package TEXT, version TEXT, severity TEXT, ghsa TEXT, title TEXT, url TEXT,
|
|
273
|
+
vulnerable_versions TEXT,
|
|
274
|
+
dev INTEGER -- 1 = the package is dev-only in this lockfile
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
-- What branch protection GATES on. Split from `repo` for the same reason
|
|
278
|
+
-- repo_security is: the rule is readable only with admin rights, so it can be
|
|
279
|
+
-- absent without corrupting the main row. NULL context_count means "not
|
|
280
|
+
-- readable"; 0 means "read, and the branch requires nothing".
|
|
281
|
+
CREATE TABLE IF NOT EXISTS branch_protection (
|
|
282
|
+
snapshot_id INTEGER, repo TEXT, branch TEXT,
|
|
283
|
+
requires_status_checks INTEGER, -- 1 on, 0 off, NULL not readable
|
|
284
|
+
strict INTEGER, -- 1 = branches must be up to date to merge
|
|
285
|
+
context_count INTEGER,
|
|
286
|
+
contexts TEXT -- newline-joined, in API order
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
-- What CI actually REPORTED, per commit we can see. `source` matters: a context
|
|
290
|
+
-- observed on the default branch but missing from a PR head is a live job the
|
|
291
|
+
-- PR's file set did not trigger (paths-gated), which is a different defect with
|
|
292
|
+
-- a different owner than a context no commit anywhere reports (stale).
|
|
293
|
+
-- Rows are the observation, not the verdict -- analyze.mjs joins them.
|
|
294
|
+
CREATE TABLE IF NOT EXISTS check_context (
|
|
295
|
+
snapshot_id INTEGER, repo TEXT,
|
|
296
|
+
source TEXT, -- 'default' | 'pr'
|
|
297
|
+
pr_number INTEGER, -- NULL when source='default'
|
|
298
|
+
name TEXT,
|
|
299
|
+
state TEXT -- conclusion/state; NULL while still running
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
-- The Actions invoice, one row per (day, product, SKU, repo) line as GitHub
|
|
303
|
+
-- reports it. This is the authoritative statement of cost; everything in
|
|
304
|
+
-- run_cost is an ATTRIBUTION of these numbers, never a replacement for them.
|
|
305
|
+
--
|
|
306
|
+
-- `gross` and `net` are both kept and must never be collapsed. A public repo on
|
|
307
|
+
-- standard runners is metered at full price and then discounted to zero:
|
|
308
|
+
-- gross == discount, net == 0. Reporting gross as money owed invents a bill;
|
|
309
|
+
-- reporting only net hides real compute and with it every kind of CI waste.
|
|
310
|
+
-- An org of mostly public repos can show a three-figure gross month and a net
|
|
311
|
+
-- of exactly zero.
|
|
312
|
+
--
|
|
313
|
+
-- `repo` is NULL on org-level lines (shared storage), which is why no foreign
|
|
314
|
+
-- key to `repo` exists here.
|
|
315
|
+
CREATE TABLE IF NOT EXISTS billing_usage (
|
|
316
|
+
snapshot_id INTEGER, date TEXT, product TEXT, sku TEXT,
|
|
317
|
+
unit_type TEXT, quantity REAL, price_per_unit REAL,
|
|
318
|
+
gross REAL, discount REAL, net REAL,
|
|
319
|
+
repo TEXT
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
-- Per-run cost, computed from job durations for the repos the invoice says
|
|
323
|
+
-- carry the spend. Absent for every other repo BY DESIGN: the analyzer reads a
|
|
324
|
+
-- missing row as "not measured" and stays silent, rather than as "free".
|
|
325
|
+
--
|
|
326
|
+
-- billable_minutes is the sum of per-job wall time rounded UP to the minute --
|
|
327
|
+
-- not the run's duration. `unpriced_minutes` is minutes on a runner class we
|
|
328
|
+
-- cannot price (larger runners, unknown labels); non-zero means cost_usd is a
|
|
329
|
+
-- floor. self_hosted_minutes is free and is tracked so it cannot be mistaken
|
|
330
|
+
-- for either.
|
|
331
|
+
CREATE TABLE IF NOT EXISTS run_cost (
|
|
332
|
+
snapshot_id INTEGER, repo TEXT, run_id INTEGER,
|
|
333
|
+
workflow_name TEXT, path TEXT, event TEXT, conclusion TEXT, created_at TEXT,
|
|
334
|
+
job_count INTEGER, billed_job_count INTEGER,
|
|
335
|
+
billable_minutes INTEGER,
|
|
336
|
+
ubuntu_minutes INTEGER, windows_minutes INTEGER, macos_minutes INTEGER,
|
|
337
|
+
self_hosted_minutes INTEGER, unpriced_minutes INTEGER,
|
|
338
|
+
cost_usd REAL
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
-- One row per job of a priced run. This is the only place the EXPANDED matrix
|
|
342
|
+
-- is visible: schema-level `workflow.job_count` counts declared jobs, and a
|
|
343
|
+
-- 3x2 matrix declares one. Telling "six jobs" from "one job, six cells" is the
|
|
344
|
+
-- difference between a workflow that complies with the matrix cap and one that
|
|
345
|
+
-- does not.
|
|
346
|
+
-- `runner_class` is cost.mjs's own verdict, stored rather than re-derived. A
|
|
347
|
+
-- LIKE against runner_labels would misread `self-hosted,macos` as billable
|
|
348
|
+
-- macOS -- free hardware charged at 10x. The labels are kept beside it as
|
|
349
|
+
-- evidence, never as the thing rules match on.
|
|
350
|
+
CREATE TABLE IF NOT EXISTS run_cost_job (
|
|
351
|
+
snapshot_id INTEGER, repo TEXT, run_id INTEGER,
|
|
352
|
+
name TEXT, runner_labels TEXT, runner_class TEXT, conclusion TEXT,
|
|
353
|
+
minutes INTEGER, cost_usd REAL
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
-- Per-repo reconciliation of run_cost against billing_usage for the same
|
|
357
|
+
-- window. `trustworthy` is the gate every cost rule checks before making a
|
|
358
|
+
-- claim: the two sides come from different endpoints, so agreement is evidence
|
|
359
|
+
-- and divergence means the priced runs are not the billed runs.
|
|
360
|
+
CREATE TABLE IF NOT EXISTS cost_reconciliation (
|
|
361
|
+
snapshot_id INTEGER, repo TEXT,
|
|
362
|
+
computed_usd REAL, billed_gross_usd REAL, billed_net_usd REAL,
|
|
363
|
+
ratio REAL, trustworthy INTEGER, reason TEXT
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
CREATE TABLE IF NOT EXISTS finding (
|
|
367
|
+
snapshot_id INTEGER, repo TEXT, code TEXT, severity TEXT,
|
|
368
|
+
category TEXT, message TEXT, evidence TEXT
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
CREATE TABLE IF NOT EXISTS collect_error (
|
|
372
|
+
snapshot_id INTEGER, repo TEXT, stage TEXT, message TEXT
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
CREATE INDEX IF NOT EXISTS idx_repo_snap ON repo(snapshot_id);
|
|
376
|
+
CREATE INDEX IF NOT EXISTS idx_issue_snap ON issue(snapshot_id, repo);
|
|
377
|
+
CREATE INDEX IF NOT EXISTS idx_pr_snap ON pull_request(snapshot_id, repo);
|
|
378
|
+
CREATE INDEX IF NOT EXISTS idx_wf_snap ON workflow(snapshot_id, repo);
|
|
379
|
+
CREATE INDEX IF NOT EXISTS idx_wfrun_snap ON workflow_run(snapshot_id, repo);
|
|
380
|
+
CREATE INDEX IF NOT EXISTS idx_finding_snap ON finding(snapshot_id, severity, code);
|
|
381
|
+
CREATE INDEX IF NOT EXISTS idx_bprot_snap ON branch_protection(snapshot_id, repo);
|
|
382
|
+
CREATE INDEX IF NOT EXISTS idx_ctx_snap ON check_context(snapshot_id, repo);
|
|
383
|
+
CREATE INDEX IF NOT EXISTS idx_bill_snap ON billing_usage(snapshot_id, repo);
|
|
384
|
+
CREATE INDEX IF NOT EXISTS idx_rcost_snap ON run_cost(snapshot_id, repo);
|
|
385
|
+
CREATE INDEX IF NOT EXISTS idx_rcjob_snap ON run_cost_job(snapshot_id, repo);
|