@mcptoolshop/housekeeping 0.0.0 → 1.3.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.
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);