@rightkit/release 0.2.121 → 0.2.122
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/build-release.mjs +6 -2
- package/cli/right-release.mjs +12 -0
- package/committed-source.mjs +200 -0
- package/ownership-approval-command.mjs +39 -0
- package/ownership-approval.mjs +370 -0
- package/ownership-command.mjs +50 -14
- package/ownership-default-policy.json +21 -0
- package/ownership-evidence.mjs +224 -0
- package/ownership-git-inventory.mjs +195 -0
- package/ownership-helper-paths.json +11 -0
- package/ownership-key-authority.mjs +85 -0
- package/ownership-key-command.mjs +530 -0
- package/ownership-key-log.mjs +185 -0
- package/ownership-key-trust.mjs +326 -0
- package/ownership-report-command.mjs +87 -0
- package/ownership-report.mjs +144 -0
- package/ownership-scan-worker.mjs +15 -0
- package/ownership.mjs +504 -197
- package/package.json +4 -1
- package/preflight.mjs +19 -16
- package/publication-gate.mjs +2 -1
- package/publish-cargo.mjs +5 -0
- package/release.mjs +1 -1
- package/rightkit-drift.mjs +112 -0
- package/rightkit-ownership.json +131 -0
- package/rightkit-sync.mjs +150 -0
- package/rightkit-versions.json +109 -26
- package/upload-release.mjs +3 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { checkApp, EXCEPTIONS_FILE } from "./ownership.mjs";
|
|
4
|
+
import { DEFAULT_APPS } from "./standalone-clone-verify.mjs";
|
|
5
|
+
import { sha256 } from "./ownership-approval.mjs";
|
|
6
|
+
|
|
7
|
+
export const OWNERSHIP_APPS = Object.freeze([
|
|
8
|
+
...DEFAULT_APPS.map(({ key, appDir }) => ({ key, root: key === "coderight" ? key : path.posix.join(key, appDir === "." ? "" : appDir), scope: key === "coderight" ? "repository" : appDir })),
|
|
9
|
+
{ key: "cutright", root: "cutright" },
|
|
10
|
+
{ key: "genright", root: "genright" },
|
|
11
|
+
{ key: "rightsites", root: "rightsites" },
|
|
12
|
+
{ key: "voiceright", root: "voiceright" },
|
|
13
|
+
{ key: "membrane", root: "coderight/context/apps/membrane-hub", scope: "context/apps/membrane-hub" },
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const canonicalJson = (value) => JSON.stringify(value, (_key, item) => item && !Array.isArray(item) && typeof item === "object"
|
|
17
|
+
? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
|
|
18
|
+
|
|
19
|
+
function catalogVersion(manifest) {
|
|
20
|
+
return String(manifest.catalogVersion ?? manifest.version ?? manifest.schema ?? "unknown");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function publishedAvailability(capability, versions) {
|
|
24
|
+
const npmPackage = capability.npmPackage ?? (capability.package?.startsWith("@rightkit/") ? capability.package : undefined);
|
|
25
|
+
const cargoPackage = capability.cargoPackage ?? (capability.package?.startsWith("rightkit-") ? capability.package : undefined);
|
|
26
|
+
const npmVersion = npmPackage && (versions.npm?.[npmPackage] ?? versions.stagedNpm?.[npmPackage]);
|
|
27
|
+
const cargoVersion = cargoPackage && (versions.cargo?.[cargoPackage] ?? versions.stagedCargo?.[cargoPackage]);
|
|
28
|
+
const swift = capability.package === "rightkit-swift" ? (versions.swift?.["rightkit-swift"] ?? versions.stagedSwift?.["rightkit-swift"]) : undefined;
|
|
29
|
+
const published = [];
|
|
30
|
+
if (npmVersion) published.push({ registry: "npm", package: npmPackage, version: npmVersion, state: versions.npm?.[npmPackage] ? "registry-pinned" : "staged-unverified" });
|
|
31
|
+
if (cargoVersion) published.push({ registry: "crates.io", package: cargoPackage, version: cargoVersion, state: versions.cargo?.[cargoPackage] ? "registry-pinned" : "staged-unverified" });
|
|
32
|
+
if (swift) published.push({ registry: "SwiftPM", package: "rightkit-swift", version: swift.version, url: swift.url, state: versions.swift?.["rightkit-swift"] ? "registry-pinned" : "staged-unverified" });
|
|
33
|
+
return published;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function statusFor(row, report) {
|
|
37
|
+
if (report.enrollmentPending && (row.status === "blocked" || row.status === "blocked-by-findings")) return "enrollment-pending";
|
|
38
|
+
if (row.status === "blocked" || row.status === "blocked-by-findings") return "blocked";
|
|
39
|
+
if (row.status === "excepted") return "excepted";
|
|
40
|
+
if (row.status === "verified-integration") return "verified-integration";
|
|
41
|
+
if (row.status === "declared-dependency" || row.uses === true) return "declared-dependency";
|
|
42
|
+
if (report.coverage?.length) return "unscanned";
|
|
43
|
+
return "not-detected";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function exceptionRequests(root, app, report) {
|
|
47
|
+
const file = path.join(root, EXCEPTIONS_FILE);
|
|
48
|
+
if (!existsSync(file)) return [];
|
|
49
|
+
let records;
|
|
50
|
+
try { records = JSON.parse(readFileSync(file, "utf8")).exceptions ?? []; } catch { return []; }
|
|
51
|
+
const payload = (record) => record?.payload ?? record;
|
|
52
|
+
const confirmed = new Set((report.covered ?? []).map((finding) => payload(finding.exception)?.requestId).filter(Boolean));
|
|
53
|
+
return records.filter((record) => !confirmed.has(payload(record)?.requestId)).map((record) => {
|
|
54
|
+
const details = payload(record);
|
|
55
|
+
const request = { app, requestId: details.requestId ?? null, fingerprint: details.fingerprint ?? null, paths: details.paths ?? [], reason: details.reason ?? null, owner: details.owner ?? null, expires: details.expires ?? null, tracking: details.trackingRef ?? details.tracking ?? null, state: "request-pending-verification" };
|
|
56
|
+
return { ...request, contentHash: sha256(canonicalJson(request)) };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function asRecord(app, root, scope, report, manifest, versions, scannedAt) {
|
|
61
|
+
const meta = report.metadata ?? {};
|
|
62
|
+
const findings = (report.findings ?? []).map((f) => ({ fingerprint: f.fingerprint, capability: f.capability, file: f.file, line: f.line ?? 0, occurrenceHash: f.occurrenceHash ?? sha256(canonicalJson(f)) }));
|
|
63
|
+
const files = [...new Set(findings.map((f) => f.file))].sort();
|
|
64
|
+
const contentDigestSource = meta.contentDigests ? "scanner-inputs" : "finding-occurrences-only";
|
|
65
|
+
const contentDigests = meta.contentDigests ?? findings.map((f) => ({ path: f.file, digest: f.occurrenceHash, kind: "finding-occurrence" }));
|
|
66
|
+
const rows = (report.rows ?? []).map((row) => {
|
|
67
|
+
const catalogEntry = manifest.capabilities.find((c) => c.capability === row.capability || c.package === row.owner);
|
|
68
|
+
return { ...row, status: statusFor(row, report), available: catalogEntry ? publishedAvailability(catalogEntry, versions) : [] };
|
|
69
|
+
});
|
|
70
|
+
const enrollmentIsPending = report.enrollmentPending === true;
|
|
71
|
+
const record = {
|
|
72
|
+
app,
|
|
73
|
+
scope: scope ?? ".",
|
|
74
|
+
status: enrollmentIsPending ? "warning" : (report.violations?.length || report.problems?.length) ? "blocked" : report.coverage?.length ? "unscanned" : "scanned",
|
|
75
|
+
enrollmentPending: enrollmentIsPending,
|
|
76
|
+
repositoryRoot: path.resolve(root),
|
|
77
|
+
repositoryIdentity: meta.repositoryIdentity ?? null,
|
|
78
|
+
commit: meta.commit ?? null,
|
|
79
|
+
host: meta.host ?? null,
|
|
80
|
+
scannedAt: meta.scannedAt ?? scannedAt,
|
|
81
|
+
scanAgeSeconds: Math.max(0, Math.floor((Date.parse(scannedAt) - Date.parse(meta.scannedAt ?? scannedAt)) / 1000)),
|
|
82
|
+
catalogVersion: meta.catalogVersion ?? catalogVersion(manifest),
|
|
83
|
+
scannerVersion: meta.scannerVersion ?? null,
|
|
84
|
+
catalogDigest: meta.catalogDigest ?? sha256(canonicalJson(manifest)),
|
|
85
|
+
contentDigestSource,
|
|
86
|
+
contentDigests,
|
|
87
|
+
scannedFilesWithFindings: files,
|
|
88
|
+
coverage: report.coverage ?? [],
|
|
89
|
+
rows,
|
|
90
|
+
findings,
|
|
91
|
+
violations: report.violations ?? [],
|
|
92
|
+
covered: report.covered ?? [],
|
|
93
|
+
problems: report.problems ?? [],
|
|
94
|
+
warnings: report.warnings ?? [],
|
|
95
|
+
};
|
|
96
|
+
record.resultDigest = sha256(canonicalJson({ app: record.app, commit: record.commit, catalogDigest: record.catalogDigest, contentDigests: record.contentDigests, coverage: record.coverage, rows: record.rows, problems: record.problems, warnings: record.warnings, enrollmentPending: record.enrollmentPending }));
|
|
97
|
+
record.exceptionRequests = exceptionRequests(root, app, report);
|
|
98
|
+
return record;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function buildOwnershipReport({ workspaceRoot, manifest, versions, apps = OWNERSHIP_APPS, now = new Date(), scanner = checkApp } = {}) {
|
|
102
|
+
if (!workspaceRoot || !manifest?.capabilities || !versions) throw new TypeError("workspaceRoot, ownership manifest, and version registry are required");
|
|
103
|
+
const scannedAt = now.toISOString();
|
|
104
|
+
const records = apps.map(({ key, root: relativeRoot, scope }) => {
|
|
105
|
+
const root = path.resolve(workspaceRoot, relativeRoot);
|
|
106
|
+
if (!existsSync(root)) return { app: key, scope: scope ?? ".", repositoryRoot: root, status: "unscanned", reason: "configured app root is missing", catalogVersion: catalogVersion(manifest), catalogDigest: sha256(canonicalJson(manifest)), scannedAt, host: null, commit: null, scanAgeSeconds: 0, rows: [], findings: [], violations: [], covered: [], problems: [], coverage: [{ path: ".", status: "unscanned", reason: "configured app root is missing" }], contentDigests: [], resultDigest: sha256(`${key}:missing`) };
|
|
107
|
+
try { return asRecord(key, root, scope, scanner(root, manifest), manifest, versions, scannedAt); }
|
|
108
|
+
catch (error) { return { app: key, scope: scope ?? ".", repositoryRoot: root, status: "unscanned", reason: error.message, catalogVersion: catalogVersion(manifest), catalogDigest: sha256(canonicalJson(manifest)), scannedAt, host: null, commit: null, scanAgeSeconds: 0, rows: [], findings: [], violations: [], covered: [], problems: [error.message], coverage: [{ path: ".", status: "unscanned", reason: error.message }], contentDigests: [], resultDigest: sha256(`${key}:${error.message}`) }; }
|
|
109
|
+
});
|
|
110
|
+
const blockMap = new Map(), requestMap = new Map(), warnings = [];
|
|
111
|
+
for (const record of records) {
|
|
112
|
+
for (const request of record.exceptionRequests ?? []) if (!requestMap.has(request.contentHash)) requestMap.set(request.contentHash, request);
|
|
113
|
+
if (record.enrollmentPending) {
|
|
114
|
+
warnings.push({ app: record.app, message: "approval enrollment pending; ownership findings remain backlog diagnostics", findings: record.violations, problems: record.problems, scannerWarnings: record.warnings });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (const item of [...record.violations, ...record.problems.map((message) => ({ kind: "scanner-problem", message }))]) {
|
|
118
|
+
const normalized = { app: record.app, ...item };
|
|
119
|
+
const contentHash = item.occurrenceHash ?? sha256(canonicalJson(item));
|
|
120
|
+
const existing = blockMap.get(contentHash);
|
|
121
|
+
if (existing) existing.apps = [...new Set([...existing.apps, record.app])].sort();
|
|
122
|
+
else blockMap.set(contentHash, { ...normalized, apps: [record.app], contentHash });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { schema: 1, generatedAt: scannedAt, catalogVersion: catalogVersion(manifest), catalogDigest: sha256(canonicalJson(manifest)), hostCoverage: [...new Set(records.map((x) => x.host).filter(Boolean))], apps: records, blocks: [...blockMap.values()], warnings, exceptionRequests: [...requestMap.values()] };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function renderOwnershipMarkdown(report) {
|
|
129
|
+
const lines = ["# RightKit ownership report", "", `Generated: ${report.generatedAt} · Catalog: ${report.catalogVersion} · Digest: ${report.catalogDigest}`, `Host coverage: ${report.hostCoverage.join(", ") || "none"}`, "", "| App | Scan scope | Status | Commit | Host | Scan age | Findings | Coverage gaps |", "|---|---|---|---|---|---:|---:|---:|"];
|
|
130
|
+
for (const app of report.apps) {
|
|
131
|
+
const states = new Set(app.rows.map((row) => row.status));
|
|
132
|
+
const state = app.status ?? (states.has("blocked") ? "blocked" : states.has("unscanned") || app.coverage.length ? "unscanned" : "scanned");
|
|
133
|
+
lines.push(`| ${app.app} | ${app.scope ?? "."} | ${state} | ${app.commit ?? "unknown"} | ${app.host ?? "unknown"} | ${app.scanAgeSeconds}s | ${app.findings.length} | ${app.coverage.length} |`);
|
|
134
|
+
}
|
|
135
|
+
lines.push("", "## Capability status", "", "| App | Capability | Status | Published availability |", "|---|---|---|---|");
|
|
136
|
+
for (const app of report.apps) for (const row of app.rows) lines.push(`| ${app.app} | ${row.capability} | ${row.status} | ${row.available.map((x) => `${x.package}@${x.version} (${x.state})`).join(", ") || "none recorded"} |`);
|
|
137
|
+
lines.push("", `Blocks: ${report.blocks.length} · Exception requests awaiting verification: ${report.exceptionRequests.length}`, "", "Exception requests are unverified records; only scanner-validated signed exceptions are reported as excepted.", "");
|
|
138
|
+
if (report.warnings?.length) {
|
|
139
|
+
lines.splice(-2, 0, "## Warnings", "", ...report.warnings.map((warning) => `- ${warning.app}: WARNING: ${warning.message}; ${warning.findings.length} finding(s), ${warning.problems.length} problem(s) retained as diagnostics`), "");
|
|
140
|
+
}
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function formatOwnershipJson(report) { return `${JSON.stringify(report, null, 2)}\n`; }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { checkApp } from "./ownership.mjs";
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const [appRoot, manifestPath] = process.argv.slice(2);
|
|
9
|
+
if (!appRoot || !manifestPath) throw new TypeError("app root and catalog path are required");
|
|
10
|
+
const report = checkApp(path.resolve(appRoot), JSON.parse(readFileSync(path.resolve(manifestPath), "utf8")));
|
|
11
|
+
process.stdout.write(`${JSON.stringify(report)}\n`);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
process.stderr.write(`${error.message}\n`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
}
|