omakit 0.6.10 → 0.6.11
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/package.json +1 -1
- package/tools/blocks/commit.json +2 -2
- package/tools/marketplace/README.md +2 -0
- package/tools/marketplace/measure-pin-grades.mjs +119 -0
- package/tools/marketplace/measure-registry-figures.mjs +160 -0
- package/tools/marketplace/pin.mjs +13 -8
- package/tools/marketplace/registry.mjs +8 -4
package/package.json
CHANGED
package/tools/blocks/commit.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"recordedAt": "2026-10-
|
|
2
|
+
"commit": "134073bfe9da1073721c0a7e9905c15dea0c7176",
|
|
3
|
+
"recordedAt": "2026-10-01T04:06:29.378Z",
|
|
4
4
|
"how": "Written by `node tools/blocks/record-commit.mjs` in the release workflow before `npm pack`, so a packaged omakit, which has no Git checkout, still names the commit its block files come from. In a checkout this file is null and `git rev-parse HEAD` is the source; a package with null here was packed without the release step, and `omakit add` refuses to stamp a header it cannot name."
|
|
5
5
|
}
|
|
@@ -19,6 +19,8 @@ local commit through the transport seam the marketplace tests itself
|
|
|
19
19
|
| `plugin.mjs` | The root files the submission contract needs, and the declared plugin identity. |
|
|
20
20
|
| `agent-control.mjs` | The recursive agent-control warning, and its remedy. |
|
|
21
21
|
| `review-cost.mjs` | The advisory review-cost verdict, shared account discovery, and path classification between dated validated snapshots. M4 and M9 carry its evidence. |
|
|
22
|
+
| `measure-registry-figures.mjs` | Reproduces the registry figures of M4 and M6 at the pin: `baselineFigures()` recorded per pin in `docs/evidence/registry-figures.json`, newest first, and rendered from that record into the literal block of `tests/unit/registry-figures.test.mjs`, the M4 block and M6 row of `docs/MEASUREMENTS.md` and the Registry facts row of `docs/UPSTREAM_CONTRACT.md`. Without `--write` it names the files not yet rendered from the record (exit 1 when one is stale); with it, it writes them. Every outcome, capability and rule name it prints is a key of the registry's data. |
|
|
23
|
+
| `measure-pin-grades.mjs` | Reproduces M7's grades: each marketplace commit in a range that touched `scripts/` or the form, graded against its parent by the shipped `comparePin()` and `pinFreshness()` three ways (tree, 0.6.6's rule; blob, 0.6.7's; value, the shipped one), every request answered from a local clone of the marketplace history, so it fetches and runs nothing. |
|
|
22
24
|
| `measure-review-cost.mjs` | Reproduces M9 across the open update population at live marketplace HEAD, with compare sources and explicit skipped reasons in JSON. |
|
|
23
25
|
| `issue.mjs` | Renders the issue the way the form would, then has the marketplace's own parser judge it. |
|
|
24
26
|
| `submit.mjs` | Assembles every check with its measured reason, and withholds the body when a blocking check fails. Three outcomes: `ready` (the body), `refused` (a blocking check failed) and `listed` (the plugin is already listed by its own repository: `identity.available` passes with the listing's record, the five body checks are omitted rather than drawn as waiting, no body exists on purpose, and `listing` carries the listed commit against the local one and the form to use for a newer commit). Decides the category and tags after the registry: a listed plugin, own or taken, is asked for neither; an unlisted one without them is asked through `ask.mjs` at a terminal, and is a usage error otherwise. Ends with `reproduce`, the command line that repeats the run without asking. Under `--offline` the validation-commit check is `skipped`, not passed: verdict `skipped`, listed under `skipped` and not `unknown`, never blocking, and the READY line says "1 check skipped (--offline)". |
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// M7 reproduction: every marketplace commit in a range that touched a pinned
|
|
2
|
+
// path other than the two data files (scripts/ and the form), graded as a
|
|
3
|
+
// change from its parent, the parent as the pin and the commit as HEAD,
|
|
4
|
+
// under the three comparisons doctor has shipped: tree, 0.6.6's (any such
|
|
5
|
+
// path's tree or blob id moved); blob, 0.6.7's (pinFreshness without the
|
|
6
|
+
// text-read values); value, the one since 0.6.10 (pinFreshness as it
|
|
7
|
+
// ships). It runs the shipped comparePin() and pinFreshness() against a
|
|
8
|
+
// local clone of the marketplace's history (a blob-filtered clone is
|
|
9
|
+
// enough) and answers each request the comparison would make from that
|
|
10
|
+
// clone, counting it: nothing is fetched by this script and nothing from
|
|
11
|
+
// the clone is run. Measured on 2026-10-01: over the seven such commits
|
|
12
|
+
// since 38060f89, the three grades it gives equal those of the v0.6.6 and
|
|
13
|
+
// v0.6.7 tags' own code (docs/evidence/pin-freshness/2026-10-01-grades.json).
|
|
14
|
+
import { execFileSync } from "node:child_process"
|
|
15
|
+
import { resolve } from "node:path"
|
|
16
|
+
import { pathToFileURL } from "node:url"
|
|
17
|
+
import { PIN_PATHS, POLICY_MODULE, pinnedReadSet, policyConstants } from "./pin.mjs"
|
|
18
|
+
import { LIVE_PATHS } from "./registry.mjs"
|
|
19
|
+
import { comparePin, pinFreshness } from "./doctor.mjs"
|
|
20
|
+
|
|
21
|
+
/** The pinned paths a grade can turn on: PIN_PATHS less the two data files read live. */
|
|
22
|
+
export const GRADED_PATHS = Object.freeze(PIN_PATHS.map((pattern) => pattern.replace(/^\/|\/$/g, "")).filter((path) => !LIVE_PATHS.includes(path)))
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The two kinds of request comparePin() makes, answered from the clone: a
|
|
26
|
+
* tree read by `git ls-tree`, a file's text at a commit by `git show`.
|
|
27
|
+
* `requests` lists each, in order.
|
|
28
|
+
*/
|
|
29
|
+
export function cloneTransport(clone) {
|
|
30
|
+
const git = (...args) => execFileSync("git", ["-C", clone, ...args], { timeout: 60_000, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 })
|
|
31
|
+
const requests = []
|
|
32
|
+
const fetchJson = async (url) => {
|
|
33
|
+
requests.push(url)
|
|
34
|
+
const sha = String(url).match(/\/git\/trees\/([0-9a-f]{40})$/)?.[1]
|
|
35
|
+
if (!sha) throw new Error(`not a tree read at an object id: ${url}`)
|
|
36
|
+
const tree = git("ls-tree", sha).split("\n").filter(Boolean).map((line) => {
|
|
37
|
+
const [meta, path] = line.split("\t")
|
|
38
|
+
const [, type, id] = meta.split(" ")
|
|
39
|
+
return { path, type, sha: id }
|
|
40
|
+
})
|
|
41
|
+
return { sha, tree, truncated: false }
|
|
42
|
+
}
|
|
43
|
+
const fetchText = async (url) => {
|
|
44
|
+
requests.push(url)
|
|
45
|
+
const [, commit, path] = String(url).match(/\/([0-9a-f]{40})\/(.+)$/) || []
|
|
46
|
+
if (!commit) throw new Error(`not a file read at a commit: ${url}`)
|
|
47
|
+
return git("show", `${commit}:${path}`)
|
|
48
|
+
}
|
|
49
|
+
return { git, requests, fetchJson, fetchText }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One change graded three ways, with the read set resolved at `pin` and what the value comparison says of it. */
|
|
53
|
+
export async function gradeChange(clone, pin, head) {
|
|
54
|
+
const transport = cloneTransport(clone)
|
|
55
|
+
const reads = pinnedReadSet(clone, undefined, (path) => transport.git("show", `${pin}:${path}`))
|
|
56
|
+
const identity = { commit: pin, ...policyConstants(transport.git("show", `${pin}:${POLICY_MODULE}`)) }
|
|
57
|
+
const comparison = await comparePin({ pinDir: clone, headCommit: head, pinCommit: pin, fetchJson: transport.fetchJson, fetchText: transport.fetchText, reads })
|
|
58
|
+
const at = { commit: head, branch: null }
|
|
59
|
+
const value = pinFreshness(identity, at, comparison)
|
|
60
|
+
const blob = pinFreshness(identity, at, { ...comparison, textReads: {} })
|
|
61
|
+
return {
|
|
62
|
+
readSet: reads.map((read) => read.path),
|
|
63
|
+
requests: transport.requests.length,
|
|
64
|
+
tree: value.evidence.pinned.length ? "advice" : "ok",
|
|
65
|
+
blob: blob.state,
|
|
66
|
+
value: value.state,
|
|
67
|
+
detail: value.detail,
|
|
68
|
+
effective: value.evidence.effective,
|
|
69
|
+
textReads: value.evidence.textReads,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Every commit in `since..until` that touched GRADED_PATHS, oldest first,
|
|
75
|
+
* each graded by gradeChange() against its first parent, and how many of
|
|
76
|
+
* those that touched a file in the read set each comparison grades advice.
|
|
77
|
+
*/
|
|
78
|
+
export async function measurePinGrades({ clone, since, until }) {
|
|
79
|
+
const git = (...args) => execFileSync("git", ["-C", clone, ...args], { timeout: 60_000, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 })
|
|
80
|
+
const range = `${git("rev-parse", since).trim()}..${git("rev-parse", until).trim()}`
|
|
81
|
+
const listed = git("log", "--reverse", "--format=%H%x09%cI%x09%s", range, "--", ...GRADED_PATHS).split("\n").filter(Boolean)
|
|
82
|
+
const commits = []
|
|
83
|
+
for (const line of listed) {
|
|
84
|
+
const [commit, committedAt, subject] = line.split("\t")
|
|
85
|
+
const parent = git("rev-parse", `${commit}^`).trim()
|
|
86
|
+
const touched = git("log", "-1", "--format=", "--name-only", commit, "--", ...GRADED_PATHS).split("\n").filter(Boolean)
|
|
87
|
+
const { readSet, ...graded } = await gradeChange(clone, parent, commit)
|
|
88
|
+
commits.push({ commit, committedAt, subject, parent, touched, inReadSet: touched.filter((path) => readSet.includes(path)), ...graded })
|
|
89
|
+
}
|
|
90
|
+
const touching = commits.filter((row) => row.inReadSet.length)
|
|
91
|
+
const advice = (rows, rule) => rows.filter((row) => row[rule] === "advice").length
|
|
92
|
+
return {
|
|
93
|
+
measurement: "M7",
|
|
94
|
+
measuredAt: new Date().toISOString(),
|
|
95
|
+
range,
|
|
96
|
+
gradedPaths: GRADED_PATHS,
|
|
97
|
+
method: "Each commit in the range that touched a graded path, graded against its first parent by the shipped comparePin() and pinFreshness(), every request answered from the local clone: tree is 0.6.6's rule (a graded path's object id moved), blob 0.6.7's (pinFreshness without text-read values), value the shipped grade.",
|
|
98
|
+
commits,
|
|
99
|
+
touchingReadSet: touching.length,
|
|
100
|
+
advice: { tree: advice(touching, "tree"), blob: advice(touching, "blob"), value: advice(touching, "value") },
|
|
101
|
+
adviceOverAll: { commits: commits.length, tree: advice(commits, "tree"), blob: advice(commits, "blob"), value: advice(commits, "value") },
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
106
|
+
const args = process.argv.slice(2)
|
|
107
|
+
const option = (name) => {
|
|
108
|
+
const index = args.indexOf(name)
|
|
109
|
+
return index >= 0 ? args[index + 1] : undefined
|
|
110
|
+
}
|
|
111
|
+
const clone = option("--clone")
|
|
112
|
+
const since = option("--since")
|
|
113
|
+
const until = option("--until") || "HEAD"
|
|
114
|
+
if (!clone || !since || args.length !== (option("--until") ? 6 : 4)) {
|
|
115
|
+
process.stderr.write("usage: node tools/marketplace/measure-pin-grades.mjs --clone <marketplace clone> --since <commit> [--until <commit>]\n")
|
|
116
|
+
process.exit(2)
|
|
117
|
+
}
|
|
118
|
+
process.stdout.write(`${JSON.stringify(await measurePinGrades({ clone: resolve(clone), since, until }), null, 2)}\n`)
|
|
119
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// M4 and M6 reproduction: the registry figures at the pin, recorded per pin
|
|
2
|
+
// in docs/evidence/registry-figures.json and rendered from that one record
|
|
3
|
+
// into the three places that cite them: the literal block in
|
|
4
|
+
// tests/unit/registry-figures.test.mjs, the M4 block and the M6 row of
|
|
5
|
+
// docs/MEASUREMENTS.md, and the Registry facts row of
|
|
6
|
+
// docs/UPSTREAM_CONTRACT.md. Measured on the 0.6.10 bump: the same
|
|
7
|
+
// figures were retyped by hand in nine files, and four of them
|
|
8
|
+
// (preflight.mjs, MARKETPLACE.md, HOW.md, INSPECT_DESIGN.md) no test reads.
|
|
9
|
+
// Without --write it prints the record entry for the pin and names each
|
|
10
|
+
// file that is not yet rendered from it; with --write it records the entry
|
|
11
|
+
// and rewrites those files.
|
|
12
|
+
//
|
|
13
|
+
// Every name in the output (outcomes, capabilities, finding rules) is a key
|
|
14
|
+
// of the registry's own data, never typed here.
|
|
15
|
+
import { execFileSync } from "node:child_process"
|
|
16
|
+
import { readFileSync, writeFileSync } from "node:fs"
|
|
17
|
+
import { join, resolve } from "node:path"
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
19
|
+
import { MARKETPLACE_PIN, requirePin } from "./pin.mjs"
|
|
20
|
+
import { baselineFigures, figure } from "./registry.mjs"
|
|
21
|
+
|
|
22
|
+
const ROOT = resolve(fileURLToPath(new URL("../..", import.meta.url)))
|
|
23
|
+
|
|
24
|
+
export const RECORD_PATH = "docs/evidence/registry-figures.json"
|
|
25
|
+
export const TEST_PATH = "tests/unit/registry-figures.test.mjs"
|
|
26
|
+
export const MEASUREMENTS_PATH = "docs/MEASUREMENTS.md"
|
|
27
|
+
export const CONTRACT_PATH = "docs/UPSTREAM_CONTRACT.md"
|
|
28
|
+
|
|
29
|
+
const TEST_BLOCK = /\/\/ registry-figures:begin[^\n]*\n[\s\S]*?\/\/ registry-figures:end\n/
|
|
30
|
+
const M4_BLOCK = /<!-- registry-figures:m4 -->\n[\s\S]*?<!-- \/registry-figures:m4 -->\n/
|
|
31
|
+
const M6_ROW = /^\| Listed sources whose validated commit was superseded at least once \|.*\|$/m
|
|
32
|
+
const CONTRACT_ROW = /^\| Registry facts \|.*\|$/m
|
|
33
|
+
|
|
34
|
+
/** The record entry for the pinned checkout: its commit, when it was committed, and baselineFigures() over it. */
|
|
35
|
+
export function entryAtPin(pinDir) {
|
|
36
|
+
const commit = execFileSync("git", ["-C", pinDir, "rev-parse", "HEAD"], { timeout: 60_000, encoding: "utf8" }).trim()
|
|
37
|
+
const committedAt = execFileSync("git", ["-C", pinDir, "log", "-1", "--format=%cI", "HEAD"], { timeout: 60_000, encoding: "utf8" }).trim()
|
|
38
|
+
return { commit, committedAt: new Date(committedAt).toISOString().replace(/\.000Z$/, "Z"), figures: baselineFigures({ pinDir }) }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The record with `entry` first, replacing an entry for the same commit; the others keep their order, newest first. */
|
|
42
|
+
export function upsert(record, entry) {
|
|
43
|
+
return { ...record, pins: [entry, ...record.pins.filter((pin) => pin.commit !== entry.commit)] }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A table of counts, largest first and by name on a tie: the order every list here is printed in. */
|
|
47
|
+
const ranked = (table) => Object.entries(table).sort(([a, x], [b, y]) => y - x || a.localeCompare(b))
|
|
48
|
+
const short = (commit) => commit.slice(0, 8)
|
|
49
|
+
const when = (iso) => `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`
|
|
50
|
+
const percent = (part, whole) => ((part / whole) * 100).toFixed(1)
|
|
51
|
+
|
|
52
|
+
/** Prose wrapped at 76 columns, the way the documents are written. */
|
|
53
|
+
function wrap(text, width = 76) {
|
|
54
|
+
const lines = []
|
|
55
|
+
let line = ""
|
|
56
|
+
for (const word of text.split(" ")) {
|
|
57
|
+
if (line && line.length + 1 + word.length > width) {
|
|
58
|
+
lines.push(line)
|
|
59
|
+
line = word
|
|
60
|
+
} else {
|
|
61
|
+
line = line ? `${line} ${word}` : word
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (line) lines.push(line)
|
|
65
|
+
return lines.join("\n")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The literal block the figures test holds the pin to. */
|
|
69
|
+
export function renderTest(text, record) {
|
|
70
|
+
const [current] = record.pins
|
|
71
|
+
const block = [
|
|
72
|
+
`// registry-figures:begin rendered from ${RECORD_PATH} by \`node tools/marketplace/measure-registry-figures.mjs --write\`; change the record, not this block`,
|
|
73
|
+
`const AT_PIN = Object.freeze(${JSON.stringify({ commit: current.commit, figures: current.figures }, null, 2)})`,
|
|
74
|
+
"// registry-figures:end",
|
|
75
|
+
"",
|
|
76
|
+
].join("\n")
|
|
77
|
+
if (!TEST_BLOCK.test(text)) throw new Error(`${TEST_PATH} has no registry-figures block to render into`)
|
|
78
|
+
return text.replace(TEST_BLOCK, () => block)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** M4's figures and the earlier pins' table, and M6's superseded row. */
|
|
82
|
+
export function renderMeasurements(text, record) {
|
|
83
|
+
const [current, ...earlier] = record.pins
|
|
84
|
+
const f = current.figures
|
|
85
|
+
const outcomes = ranked(f.outcomes)
|
|
86
|
+
const lines = [
|
|
87
|
+
"<!-- registry-figures:m4 -->",
|
|
88
|
+
wrap(`From \`registry.json\` at the pin (\`${short(current.commit)}\`, ${when(current.committedAt)}): ${figure(f.sources)} listed sources, of which ${figure(f.withBaseline)} carry a recorded baseline (the rest were listed before the baseline existed or have no record):`),
|
|
89
|
+
"",
|
|
90
|
+
"| Outcome | Count |",
|
|
91
|
+
"| --- | --- |",
|
|
92
|
+
...outcomes.map(([name, n]) => `| \`${name}\` | ${figure(n)} |`),
|
|
93
|
+
`| Findings ever recorded, total | ${figure(f.findingsTotal)} (${ranked(f.findings).map(([name, n]) => `\`${name}\` ${figure(n)}`).join(", ")}) |`,
|
|
94
|
+
"",
|
|
95
|
+
wrap(`Capabilities recorded: ${ranked(f.capabilities).map(([name, n]) => `${name} ${figure(n)}`).join(", ")}.`),
|
|
96
|
+
]
|
|
97
|
+
if (earlier.length) {
|
|
98
|
+
lines.push(
|
|
99
|
+
"",
|
|
100
|
+
"At the earlier pins, counted the same way from the same two files:",
|
|
101
|
+
"",
|
|
102
|
+
`| Pin | Sources | With a baseline | ${outcomes.map(([name]) => `\`${name}\``).join(" | ")} |`,
|
|
103
|
+
`| --- | --- | --- | ${outcomes.map(() => "---").join(" | ")} |`,
|
|
104
|
+
...earlier.map((pin) => `| \`${short(pin.commit)}\` (${when(pin.committedAt)}) | ${figure(pin.figures.sources)} | ${figure(pin.figures.withBaseline)} | ${outcomes.map(([name]) => figure(pin.figures.outcomes[name] || 0)).join(" | ")} |`),
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
lines.push("<!-- /registry-figures:m4 -->", "")
|
|
108
|
+
const superseded = (pin) => `${figure(pin.figures.superseded.sources)} of ${figure(pin.figures.sources)}, ${percent(pin.figures.superseded.sources, pin.figures.sources)}%, ${figure(pin.figures.superseded.commits)} commits`
|
|
109
|
+
const row = `| Listed sources whose validated commit was superseded at least once | ${figure(f.superseded.sources)} of ${figure(f.sources)} (${percent(f.superseded.sources, f.sources)}%) at pin \`${short(current.commit)}\`, ${figure(f.superseded.commits)} superseded commits, one source revalidated ${f.superseded.most} times${earlier.length ? ` (${earlier.map((pin) => `at pin \`${short(pin.commit)}\`: ${superseded(pin)}`).join("; ")})` : ""} |`
|
|
110
|
+
if (!M4_BLOCK.test(text)) throw new Error(`${MEASUREMENTS_PATH} has no registry-figures:m4 block to render into`)
|
|
111
|
+
if (!M6_ROW.test(text)) throw new Error(`${MEASUREMENTS_PATH} has no superseded row in M6 to render into`)
|
|
112
|
+
return text.replace(M4_BLOCK, () => lines.join("\n")).replace(M6_ROW, () => row)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The Registry facts row of the pinned-checkout table. */
|
|
116
|
+
export function renderContract(text, record) {
|
|
117
|
+
const f = record.pins[0].figures
|
|
118
|
+
const row = `| Registry facts | ${figure(f.listingValidated)} sources with \`listingValidatedCommit\`; ${figure(f.withBaseline)} with an \`automatedSecurityBaseline\` record (${ranked(f.outcomes).map(([name, n]) => `${figure(n)} ${name}`).join(", ")}); ${figure(f.catalogPlugins)} catalog plugin ids; ${figure(f.retiredIds)} retired ids; ${figure(f.superseded.sources)} sources (${percent(f.superseded.sources, f.sources)}%) with at least one superseded validated commit, ${figure(f.superseded.commits)} superseded commits in all |`
|
|
119
|
+
if (!CONTRACT_ROW.test(text)) throw new Error(`${CONTRACT_PATH} has no Registry facts row to render into`)
|
|
120
|
+
return text.replace(CONTRACT_ROW, () => row)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Each file the record renders into, as it is and as the record would have it. */
|
|
124
|
+
export function renderAll(record, read = (path) => readFileSync(join(ROOT, path), "utf8")) {
|
|
125
|
+
return [
|
|
126
|
+
{ path: RECORD_PATH, now: read(RECORD_PATH), next: `${JSON.stringify(record, null, 2)}\n` },
|
|
127
|
+
{ path: TEST_PATH, now: read(TEST_PATH), next: renderTest(read(TEST_PATH), record) },
|
|
128
|
+
{ path: MEASUREMENTS_PATH, now: read(MEASUREMENTS_PATH), next: renderMeasurements(read(MEASUREMENTS_PATH), record) },
|
|
129
|
+
{ path: CONTRACT_PATH, now: read(CONTRACT_PATH), next: renderContract(read(CONTRACT_PATH), record) },
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The pin's entry recorded and every file rendered from the record. With
|
|
135
|
+
* `write`, the files that differ are written; without, nothing is. Returns
|
|
136
|
+
* the entry and, per file, whether it was (or would be) rewritten.
|
|
137
|
+
*/
|
|
138
|
+
export function measureRegistryFigures({ repoRoot = ROOT, pinDir = requirePin(repoRoot).dir, write = false } = {}) {
|
|
139
|
+
const entry = entryAtPin(pinDir)
|
|
140
|
+
if (entry.commit !== MARKETPLACE_PIN.commit) throw new Error(`the checkout at ${pinDir} is at ${entry.commit}, not the pin ${MARKETPLACE_PIN.commit}`)
|
|
141
|
+
const read = (path) => readFileSync(join(repoRoot, path), "utf8")
|
|
142
|
+
const record = upsert(JSON.parse(read(RECORD_PATH)), entry)
|
|
143
|
+
const files = renderAll(record, read)
|
|
144
|
+
if (write) {
|
|
145
|
+
for (const file of files.filter((candidate) => candidate.next !== candidate.now)) writeFileSync(join(repoRoot, file.path), file.next)
|
|
146
|
+
}
|
|
147
|
+
return { entry, files: Object.fromEntries(files.map((file) => [file.path, file.next === file.now ? "current" : write ? "written" : "stale"])) }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
151
|
+
const args = process.argv.slice(2)
|
|
152
|
+
const unknown = args.filter((arg) => arg !== "--write")
|
|
153
|
+
if (unknown.length) {
|
|
154
|
+
process.stderr.write(`usage: node tools/marketplace/measure-registry-figures.mjs [--write]; not ${unknown.join(" ")}\n`)
|
|
155
|
+
process.exit(2)
|
|
156
|
+
}
|
|
157
|
+
const result = measureRegistryFigures({ write: args.includes("--write") })
|
|
158
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
159
|
+
if (!args.includes("--write") && Object.values(result.files).includes("stale")) process.exitCode = 1
|
|
160
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// It fetches only what omakit reads. The marketplace at this commit is 325 MB,
|
|
6
6
|
// of which 168 MB is preview imagery and 151 MB is history, and omakit reads
|
|
7
7
|
// twenty files out of it, sixteen of them under scripts/ (PIN_READS). A
|
|
8
|
-
// blob-filtered, sparsely checked out fetch of just those paths
|
|
8
|
+
// blob-filtered, sparsely checked out fetch of just those paths was 15 MB and
|
|
9
9
|
// takes 2 seconds instead of 17; at pin b441b4f0 (2026-10-01) the same fetch
|
|
10
10
|
// was 23 MB on disk in 3.1 seconds. PIN_PATHS below is the whole list, and
|
|
11
11
|
// tests/unit/pin.test.mjs fails if any module starts reading a path outside
|
|
@@ -32,6 +32,9 @@ export const MARKETPLACE_PIN = Object.freeze({
|
|
|
32
32
|
commitSubject: "Add LINE for Omarchy plugin (#9470)",
|
|
33
33
|
baselineVersion: "3",
|
|
34
34
|
enforcementMode: "selective",
|
|
35
|
+
// What a fresh sparse fetch of this commit measured on disk, for the one
|
|
36
|
+
// line `omakit pin` prints before it fetches. A pin bump re-measures it.
|
|
37
|
+
fetchedMb: 23,
|
|
35
38
|
})
|
|
36
39
|
|
|
37
40
|
/**
|
|
@@ -179,10 +182,12 @@ export function unfollowedImports(pinDir, reads = pinnedReadSet(pinDir)) {
|
|
|
179
182
|
* a package (`sharp`) is not followed: PIN_PATHS does not fetch it, so
|
|
180
183
|
* omakit could not read it. Measured at pins b7b29654 and b441b4f0: 16 of
|
|
181
184
|
* the 34 files under scripts/, 10 opened by omakit and 6 imported by those.
|
|
185
|
+
* `text` reads one file's text by its path; it defaults to the checkout in
|
|
186
|
+
* `pinDir`, and measure-pin-grades.mjs hands in a commit's blobs instead.
|
|
182
187
|
*
|
|
183
188
|
* @returns {{ path: string, imported: boolean, via: string|null }[]}
|
|
184
189
|
*/
|
|
185
|
-
export function pinnedReadSet(pinDir, reads = PIN_READS) {
|
|
190
|
+
export function pinnedReadSet(pinDir, reads = PIN_READS, text = (path) => readFileSync(join(pinDir, path), "utf8")) {
|
|
186
191
|
const set = new Map()
|
|
187
192
|
const queue = []
|
|
188
193
|
for (const read of reads) {
|
|
@@ -191,13 +196,13 @@ export function pinnedReadSet(pinDir, reads = PIN_READS) {
|
|
|
191
196
|
}
|
|
192
197
|
while (queue.length) {
|
|
193
198
|
const from = queue.shift()
|
|
194
|
-
let
|
|
199
|
+
let source
|
|
195
200
|
try {
|
|
196
|
-
|
|
201
|
+
source = text(from)
|
|
197
202
|
} catch (error) {
|
|
198
203
|
throw new PinError(`cannot read ${from} from the pinned checkout at ${pinDir}: ${error.message}`)
|
|
199
204
|
}
|
|
200
|
-
for (const match of
|
|
205
|
+
for (const match of source.matchAll(RELATIVE_IMPORT)) {
|
|
201
206
|
const path = posix.normalize(posix.join(posix.dirname(from), match[1]))
|
|
202
207
|
if (!path.startsWith("scripts/") || set.has(path)) continue
|
|
203
208
|
set.set(path, { path, imported: true, via: from })
|
|
@@ -232,7 +237,7 @@ function pinMigration(repoRoot, env = process.env) {
|
|
|
232
237
|
if (!existsSync(join(oldDir, ".git")) || existsSync(newDir)) return null
|
|
233
238
|
const remedy = `mkdir -p -- ${shellQuote(dirname(newDir))} && mv -- ${shellQuote(oldDir)} ${shellQuote(newDir)}`
|
|
234
239
|
return new PinError(
|
|
235
|
-
`the marketplace pin is still at the old in-repository location ${oldDir}; the user-writable location ${newDir} does not exist. Omakit will not move the
|
|
240
|
+
`the marketplace pin is still at the old in-repository location ${oldDir}; the user-writable location ${newDir} does not exist. Omakit will not move the checkout (${pinDiskUsage(oldDir, env)}) without you.`,
|
|
236
241
|
{ code: "marketplace-pin-migration-required", remedy },
|
|
237
242
|
)
|
|
238
243
|
}
|
|
@@ -409,7 +414,7 @@ function populatePin(dir, log) {
|
|
|
409
414
|
mkdirSync(dir, { recursive: true })
|
|
410
415
|
execFileSync("git", ["init", "-q", dir], { timeout: 60_000, encoding: "utf8" })
|
|
411
416
|
git(dir, ["remote", "add", "origin", MARKETPLACE_PIN.repository])
|
|
412
|
-
log({ state: "fetching", text: `fetching the pinned marketplace checkout, ${MARKETPLACE_PIN.commit.slice(0, 7)}, about
|
|
417
|
+
log({ state: "fetching", text: `fetching the pinned marketplace checkout, ${MARKETPLACE_PIN.commit.slice(0, 7)}, about ${MARKETPLACE_PIN.fetchedMb} MB` })
|
|
413
418
|
git(dir, ["config", "core.sparseCheckout", "true"])
|
|
414
419
|
mkdirSync(join(dir, ".git/info"), { recursive: true })
|
|
415
420
|
writeFileSync(join(dir, ".git/info/sparse-checkout"), `${PIN_PATHS.join("\n")}\n`)
|
|
@@ -524,7 +529,7 @@ export function ensurePin(repoRoot, log = () => {}, env = process.env, { populat
|
|
|
524
529
|
export function pinDiskUsage(dir, env = process.env) {
|
|
525
530
|
// -H follows a symlink given on the command line (POSIX; GNU's -D). Measured
|
|
526
531
|
// without it: a checkout reached through a symlink reported 0.0 MB, the size
|
|
527
|
-
// of the link, while the directory behind it was 15 MB.
|
|
532
|
+
// of the link, while the directory behind it was 15 MB (at pin 38060f89).
|
|
528
533
|
//
|
|
529
534
|
// The total is read whether or not du exited 0. du exits 1 when a file it
|
|
530
535
|
// listed is gone by the time it reaches it, and still prints the total.
|
|
@@ -373,12 +373,13 @@ export function idUniverse(options = {}) {
|
|
|
373
373
|
/**
|
|
374
374
|
* The recorded baseline over every listed source, counted from registry.json at
|
|
375
375
|
* the pin. This is the one place the figures cited by `baseline.preflight` and
|
|
376
|
-
* docs/MEASUREMENTS.md M4 come from;
|
|
377
|
-
*
|
|
378
|
-
* stale literal
|
|
376
|
+
* docs/MEASUREMENTS.md M4 come from; measure-registry-figures.mjs records
|
|
377
|
+
* them per pin and renders the test and the two documents that cite them, so
|
|
378
|
+
* a pin bump changes the printed number rather than leaving a stale literal
|
|
379
|
+
* behind.
|
|
379
380
|
*
|
|
380
381
|
* @param {{ repoRoot?: string, pinDir?: string }} [options]
|
|
381
|
-
* @returns {{ sources: number, withBaseline: number, outcomes: Record<string, number>,
|
|
382
|
+
* @returns {{ sources: number, listingValidated: number, withBaseline: number, outcomes: Record<string, number>,
|
|
382
383
|
* capabilities: Record<string, number>, findings: Record<string, number>,
|
|
383
384
|
* findingsTotal: number, superseded: { sources: number, commits: number, most: number },
|
|
384
385
|
* retiredIds: number, catalogPlugins: number }}
|
|
@@ -393,11 +394,13 @@ export function baselineFigures(options = {}) {
|
|
|
393
394
|
const capabilities = {}
|
|
394
395
|
const findings = {}
|
|
395
396
|
let withBaseline = 0
|
|
397
|
+
let listingValidated = 0
|
|
396
398
|
let findingsTotal = 0
|
|
397
399
|
let supersededSources = 0
|
|
398
400
|
let supersededCommits = 0
|
|
399
401
|
let most = 0
|
|
400
402
|
for (const source of sources) {
|
|
403
|
+
if (typeof source?.listingValidatedCommit === "string" && source.listingValidatedCommit) listingValidated += 1
|
|
401
404
|
const baseline = source?.automatedSecurityBaseline
|
|
402
405
|
if (baseline && typeof baseline.outcome === "string") {
|
|
403
406
|
withBaseline += 1
|
|
@@ -415,6 +418,7 @@ export function baselineFigures(options = {}) {
|
|
|
415
418
|
}
|
|
416
419
|
return {
|
|
417
420
|
sources: sources.length,
|
|
421
|
+
listingValidated,
|
|
418
422
|
withBaseline,
|
|
419
423
|
outcomes,
|
|
420
424
|
capabilities,
|