@topogram/cli 0.3.64 → 0.3.65
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/src/adoption/plan/index.js +703 -0
- package/src/adoption/plan.js +12 -703
- package/src/agent-ops/query-builders/auth.js +375 -0
- package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
- package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
- package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
- package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
- package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
- package/src/agent-ops/query-builders/change-risk.js +25 -0
- package/src/agent-ops/query-builders/common.js +149 -0
- package/src/agent-ops/query-builders/maintained-risk.js +539 -0
- package/src/agent-ops/query-builders/maintained-shared.js +120 -0
- package/src/agent-ops/query-builders/multi-agent.js +547 -0
- package/src/agent-ops/query-builders/projection-impacts.js +514 -0
- package/src/agent-ops/query-builders/work-packets.js +417 -0
- package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
- package/src/agent-ops/query-builders/workflow-context.js +398 -0
- package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -26
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +60 -0
- package/src/catalog/diagnostics.js +15 -0
- package/src/catalog/entries.js +42 -0
- package/src/catalog/files.js +67 -0
- package/src/catalog/provenance.js +122 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +2 -0
- package/src/catalog.js +18 -746
- package/src/cli/commands/catalog/check.js +31 -0
- package/src/cli/commands/catalog/copy.js +59 -0
- package/src/cli/commands/catalog/doctor.js +248 -0
- package/src/cli/commands/catalog/help.js +21 -0
- package/src/cli/commands/catalog/list.js +52 -0
- package/src/cli/commands/catalog/runner.js +92 -0
- package/src/cli/commands/catalog/shared.js +17 -0
- package/src/cli/commands/catalog/show.js +134 -0
- package/src/cli/commands/catalog.js +30 -615
- package/src/cli/commands/generator-policy/package-info.js +162 -0
- package/src/cli/commands/generator-policy/payloads.js +372 -0
- package/src/cli/commands/generator-policy/printers.js +159 -0
- package/src/cli/commands/generator-policy/runner.js +81 -0
- package/src/cli/commands/generator-policy/shared.js +39 -0
- package/src/cli/commands/generator-policy.js +15 -783
- package/src/cli/commands/import/adopt.js +170 -0
- package/src/cli/commands/import/check.js +91 -0
- package/src/cli/commands/import/diff.js +84 -0
- package/src/cli/commands/import/help.js +47 -0
- package/src/cli/commands/import/paths.js +277 -0
- package/src/cli/commands/import/plan.js +284 -0
- package/src/cli/commands/import/refresh.js +470 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +230 -0
- package/src/cli/commands/import.js +33 -1732
- package/src/cli/commands/package/constants.js +17 -0
- package/src/cli/commands/package/doctor.js +240 -0
- package/src/cli/commands/package/help.js +27 -0
- package/src/cli/commands/package/lockfile.js +135 -0
- package/src/cli/commands/package/npm.js +97 -0
- package/src/cli/commands/package/reporting.js +35 -0
- package/src/cli/commands/package/runner.js +33 -0
- package/src/cli/commands/package/shared.js +9 -0
- package/src/cli/commands/package/update-cli.js +252 -0
- package/src/cli/commands/package/versions.js +35 -0
- package/src/cli/commands/package.js +29 -813
- package/src/cli/commands/query/change-plan.js +68 -0
- package/src/cli/commands/query/definitions.js +202 -0
- package/src/cli/commands/query/import-adopt.js +121 -0
- package/src/cli/commands/query/runner/artifacts.js +102 -0
- package/src/cli/commands/query/runner/boundaries.js +211 -0
- package/src/cli/commands/query/runner/change.js +182 -0
- package/src/cli/commands/query/runner/import-adopt.js +111 -0
- package/src/cli/commands/query/runner/index.js +31 -0
- package/src/cli/commands/query/runner/output.js +12 -0
- package/src/cli/commands/query/runner/workflow.js +241 -0
- package/src/cli/commands/query/runner.js +3 -0
- package/src/cli/commands/query/workflow-context.js +5 -0
- package/src/cli/commands/query/workspace.js +274 -0
- package/src/cli/commands/query.js +9 -1300
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +466 -0
- package/src/cli/commands/template/constants.js +8 -0
- package/src/cli/commands/template/diagnostics.js +26 -0
- package/src/cli/commands/template/help.js +28 -0
- package/src/cli/commands/template/lifecycle.js +404 -0
- package/src/cli/commands/template/list-show.js +287 -0
- package/src/cli/commands/template/policy.js +422 -0
- package/src/cli/commands/template/shared.js +127 -0
- package/src/cli/commands/template/updates.js +352 -0
- package/src/cli/commands/template.js +41 -2143
- package/src/generator/api/contracts.js +497 -0
- package/src/generator/api/metadata.js +221 -0
- package/src/generator/api/openapi.js +559 -0
- package/src/generator/api/schema.js +124 -0
- package/src/generator/api/types.d.ts +98 -0
- package/src/generator/api.js +3 -1195
- package/src/generator/context/shared/domain-sdlc.js +282 -0
- package/src/generator/context/shared/maintained-boundary.js +665 -0
- package/src/generator/context/shared/metrics.js +85 -0
- package/src/generator/context/shared/primitives.js +64 -0
- package/src/generator/context/shared/relationships.js +453 -0
- package/src/generator/context/shared/summaries.js +263 -0
- package/src/generator/context/shared/types.d.ts +207 -0
- package/src/generator/context/shared.d.ts +42 -0
- package/src/generator/context/shared.js +80 -1390
- package/src/generator/context/slice/core.js +397 -0
- package/src/generator/context/slice/sdlc.js +417 -0
- package/src/generator/context/slice/ui-packets.js +183 -0
- package/src/generator/context/slice.js +2 -859
- package/src/generator/registry/index.js +507 -0
- package/src/generator/registry.js +18 -504
- package/src/generator/runtime/environment/index.js +666 -0
- package/src/generator/runtime/environment.js +4 -666
- package/src/generator/runtime/runtime-check/index.js +554 -0
- package/src/generator/runtime/runtime-check.js +4 -554
- package/src/generator/runtime/shared/index.js +572 -0
- package/src/generator/runtime/shared.js +19 -570
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/shared.d.ts +3 -0
- package/src/generator/widget-conformance/behavior-report.js +258 -0
- package/src/generator/widget-conformance/checks.js +371 -0
- package/src/generator/widget-conformance/projection-context.js +200 -0
- package/src/generator/widget-conformance/report.js +166 -0
- package/src/generator/widget-conformance/types.d.ts +121 -0
- package/src/generator/widget-conformance.js +3 -824
- package/src/import/core/context.d.ts +3 -0
- package/src/import/core/contracts.d.ts +1 -0
- package/src/import/core/registry.d.ts +4 -0
- package/src/import/core/runner/candidates.js +217 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +50 -0
- package/src/import/core/runner/run.js +79 -0
- package/src/import/core/runner/tracks.js +150 -0
- package/src/import/core/runner/ui-drafts.js +337 -0
- package/src/import/core/runner.js +3 -698
- package/src/import/core/shared/api-routes.js +221 -0
- package/src/import/core/shared/candidates.js +97 -0
- package/src/import/core/shared/files.js +177 -0
- package/src/import/core/shared/next-app.js +389 -0
- package/src/import/core/shared/types.d.ts +51 -0
- package/src/import/core/shared/ui-routes.js +230 -0
- package/src/import/core/shared.js +60 -861
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +83 -0
- package/src/new-project/json.js +28 -0
- package/src/new-project/metadata.js +96 -0
- package/src/new-project/package-spec.js +161 -0
- package/src/new-project/project-files.js +348 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +368 -0
- package/src/new-project/template-snapshots.js +430 -0
- package/src/new-project/template-updates.js +512 -0
- package/src/new-project/types.d.ts +83 -0
- package/src/new-project.js +6 -2277
- package/src/parser.d.ts +87 -1
- package/src/parser.js +118 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +564 -0
- package/src/project-config.js +19 -561
- package/src/resolver/enrich/acceptance-criterion.js +2 -0
- package/src/resolver/enrich/bug.js +2 -0
- package/src/resolver/enrich/pitch.js +2 -0
- package/src/resolver/enrich/requirement.js +2 -0
- package/src/resolver/enrich/task.js +2 -0
- package/src/resolver/index.js +19 -2089
- package/src/resolver/normalize.js +384 -1
- package/src/resolver/plans.js +168 -0
- package/src/resolver/projections-api.js +494 -0
- package/src/resolver/projections-db.js +133 -0
- package/src/resolver/projections-ui.js +317 -0
- package/src/resolver/shapes.js +251 -0
- package/src/resolver/shared.js +278 -0
- package/src/resolver/widgets.js +132 -0
- package/src/template-trust/constants.js +62 -0
- package/src/template-trust/content.js +258 -0
- package/src/template-trust/diff.js +92 -0
- package/src/template-trust/policy.js +61 -0
- package/src/template-trust/record.js +90 -0
- package/src/template-trust/status.js +182 -0
- package/src/template-trust.js +24 -687
- package/src/text-helpers.d.ts +1 -0
- package/src/topogram-types.d.ts +69 -0
- package/src/validator/common.js +488 -0
- package/src/validator/data-model.js +237 -0
- package/src/validator/docs.js +167 -0
- package/src/validator/expressions.js +146 -1
- package/src/validator/index.d.ts +23 -0
- package/src/validator/index.js +32 -3585
- package/src/validator/kinds.d.ts +41 -0
- package/src/validator/kinds.js +2 -0
- package/src/validator/model-helpers.js +46 -0
- package/src/validator/per-kind/acceptance-criterion.js +5 -0
- package/src/validator/per-kind/bug.js +6 -0
- package/src/validator/per-kind/domain.js +15 -2
- package/src/validator/per-kind/pitch.js +7 -0
- package/src/validator/per-kind/requirement.js +5 -0
- package/src/validator/per-kind/task.js +7 -0
- package/src/validator/per-kind/widget.js +14 -0
- package/src/validator/projections/api-http-async.js +410 -0
- package/src/validator/projections/api-http-authz.js +88 -0
- package/src/validator/projections/api-http-core.js +205 -0
- package/src/validator/projections/api-http-policies.js +339 -0
- package/src/validator/projections/api-http-responses.js +233 -0
- package/src/validator/projections/api-http.js +44 -0
- package/src/validator/projections/db.js +353 -0
- package/src/validator/projections/generator-defaults.js +45 -0
- package/src/validator/projections/helpers.js +87 -0
- package/src/validator/projections/ui-helpers.js +214 -0
- package/src/validator/projections/ui-navigation.js +344 -0
- package/src/validator/projections/ui-structure.js +364 -0
- package/src/validator/projections/ui-widgets.js +493 -0
- package/src/validator/projections/ui.js +46 -0
- package/src/validator/registry.js +48 -1
- package/src/validator/utils.d.ts +20 -0
- package/src/validator/utils.js +115 -12
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/import-app/api/collect.js +221 -0
- package/src/workflows/import-app/api/openapi.js +257 -0
- package/src/workflows/import-app/api/routes.js +327 -0
- package/src/workflows/import-app/api/sources.js +22 -0
- package/src/workflows/import-app/api.js +2 -797
- package/src/workflows/reconcile/adoption-plan/build.js +208 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
- package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
- package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
- package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
- package/src/workflows/reconcile/adoption-plan.js +30 -740
- package/src/workflows/reconcile/auth/closures.js +115 -0
- package/src/workflows/reconcile/auth/formatters.js +142 -0
- package/src/workflows/reconcile/auth/inference.js +330 -0
- package/src/workflows/reconcile/auth/roles.js +122 -0
- package/src/workflows/reconcile/auth.js +35 -690
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +12 -598
- package/src/workflows/reconcile/canonical-surface.js +1 -1
- package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
- package/src/workflows/reconcile/impacts/indexes.js +101 -0
- package/src/workflows/reconcile/impacts/patches.js +252 -0
- package/src/workflows/reconcile/impacts/reports.js +80 -0
- package/src/workflows/reconcile/impacts.js +14 -623
- package/src/workspace-docs.d.ts +29 -0
package/src/template-trust.js
CHANGED
|
@@ -1,689 +1,26 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* @returns {boolean}
|
|
28
|
-
*/
|
|
29
|
-
function isSameOrInside(parent, child) {
|
|
30
|
-
const relative = path.relative(path.resolve(parent), path.resolve(child));
|
|
31
|
-
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {string} value
|
|
36
|
-
* @returns {string}
|
|
37
|
-
*/
|
|
38
|
-
function normalizeRoot(value) {
|
|
39
|
-
return String(value || "").replace(/\\/g, "/");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {string} value
|
|
44
|
-
* @returns {string}
|
|
45
|
-
*/
|
|
46
|
-
function normalizeRelativePath(value) {
|
|
47
|
-
return value.replace(/\\/g, "/");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param {string} relativePath
|
|
52
|
-
* @returns {string}
|
|
53
|
-
*/
|
|
54
|
-
function unsupportedImplementationSymlinkMessage(relativePath) {
|
|
55
|
-
return `Template implementation contains unsupported symlink '${relativePath}'. Implementation trust hashes real files under implementation/; symlinks can point outside the trusted root. Replace symlinks with real files under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @param {string} modulePath
|
|
60
|
-
* @returns {string}
|
|
61
|
-
*/
|
|
62
|
-
function implementationOutsideRootMessage(modulePath) {
|
|
63
|
-
return `Template implementation module '${modulePath}' must be under implementation/ for template-attached projects. Keep executable template code inside implementation/ so the trust record covers what topogram generate may load. Move the module back under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @param {string|string[]} issueOrIssues
|
|
68
|
-
* @returns {string}
|
|
69
|
-
*/
|
|
70
|
-
export function templateTrustRecoveryGuidance(issueOrIssues) {
|
|
71
|
-
const issues = Array.isArray(issueOrIssues) ? issueOrIssues : [issueOrIssues];
|
|
72
|
-
const text = issues.join("\n");
|
|
73
|
-
if (issues.length > 0 && issues.every((issue) =>
|
|
74
|
-
issue.includes("topogram trust status") &&
|
|
75
|
-
issue.includes("topogram trust diff") &&
|
|
76
|
-
issue.includes("topogram trust template")
|
|
77
|
-
)) {
|
|
78
|
-
return "";
|
|
79
|
-
}
|
|
80
|
-
if (text.includes("unsupported symlink")) {
|
|
81
|
-
return `Replace symlinks with real files under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
|
|
82
|
-
}
|
|
83
|
-
if (text.includes("must be under implementation/")) {
|
|
84
|
-
return `Keep executable template code under implementation/ so it can be hashed and trusted; move the module back under implementation/, then run ${TRUST_REVIEW_COMMANDS} after review.`;
|
|
85
|
-
}
|
|
86
|
-
return `Run \`topogram trust status\` and \`topogram trust diff\` to review implementation changes; after review, run \`topogram trust template\` to trust the current files.`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* @param {string} value
|
|
91
|
-
* @returns {string}
|
|
92
|
-
*/
|
|
93
|
-
function escapeDiffPath(value) {
|
|
94
|
-
return value.replace(/\t/g, "\\t").replace(/\n/g, "\\n");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @param {any} bytes
|
|
99
|
-
* @returns {boolean}
|
|
100
|
-
*/
|
|
101
|
-
function isLikelyText(bytes) {
|
|
102
|
-
if (bytes.includes(0)) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
const length = Math.min(bytes.length, 4096);
|
|
106
|
-
let suspicious = 0;
|
|
107
|
-
for (let index = 0; index < length; index += 1) {
|
|
108
|
-
const byte = bytes[index];
|
|
109
|
-
if (byte === 9 || byte === 10 || byte === 13) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (byte < 32 || byte === 127) {
|
|
113
|
-
suspicious += 1;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return length === 0 || suspicious / length < 0.02;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* @param {string} text
|
|
121
|
-
* @returns {string[]}
|
|
122
|
-
*/
|
|
123
|
-
function linesForDiff(text) {
|
|
124
|
-
const lines = text.split("\n");
|
|
125
|
-
if (lines.at(-1) === "") {
|
|
126
|
-
lines.pop();
|
|
127
|
-
}
|
|
128
|
-
return lines;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* @param {string[]} before
|
|
133
|
-
* @param {string[]} after
|
|
134
|
-
* @returns {Array<{ type: "same"|"added"|"removed", text: string }>}
|
|
135
|
-
*/
|
|
136
|
-
function diffLines(before, after) {
|
|
137
|
-
const rows = before.length;
|
|
138
|
-
const columns = after.length;
|
|
139
|
-
/** @type {number[][]} */
|
|
140
|
-
const table = Array.from({ length: rows + 1 }, () => Array(columns + 1).fill(0));
|
|
141
|
-
for (let row = rows - 1; row >= 0; row -= 1) {
|
|
142
|
-
for (let column = columns - 1; column >= 0; column -= 1) {
|
|
143
|
-
table[row][column] = before[row] === after[column]
|
|
144
|
-
? table[row + 1][column + 1] + 1
|
|
145
|
-
: Math.max(table[row + 1][column], table[row][column + 1]);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/** @type {Array<{ type: "same"|"added"|"removed", text: string }>} */
|
|
149
|
-
const changes = [];
|
|
150
|
-
let row = 0;
|
|
151
|
-
let column = 0;
|
|
152
|
-
while (row < rows && column < columns) {
|
|
153
|
-
if (before[row] === after[column]) {
|
|
154
|
-
changes.push({ type: "same", text: before[row] });
|
|
155
|
-
row += 1;
|
|
156
|
-
column += 1;
|
|
157
|
-
} else if (table[row + 1][column] >= table[row][column + 1]) {
|
|
158
|
-
changes.push({ type: "removed", text: before[row] });
|
|
159
|
-
row += 1;
|
|
160
|
-
} else {
|
|
161
|
-
changes.push({ type: "added", text: after[column] });
|
|
162
|
-
column += 1;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
while (row < rows) {
|
|
166
|
-
changes.push({ type: "removed", text: before[row] });
|
|
167
|
-
row += 1;
|
|
168
|
-
}
|
|
169
|
-
while (column < columns) {
|
|
170
|
-
changes.push({ type: "added", text: after[column] });
|
|
171
|
-
column += 1;
|
|
172
|
-
}
|
|
173
|
-
return changes;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @param {string} relativePath
|
|
178
|
-
* @param {string|null} beforeText
|
|
179
|
-
* @param {string|null} afterText
|
|
180
|
-
* @returns {string|null}
|
|
181
|
-
*/
|
|
182
|
-
function unifiedTextDiff(relativePath, beforeText, afterText) {
|
|
183
|
-
if (beforeText === null && afterText === null) {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
const beforeLines = beforeText === null ? [] : linesForDiff(beforeText);
|
|
187
|
-
const afterLines = afterText === null ? [] : linesForDiff(afterText);
|
|
188
|
-
const changes = diffLines(beforeLines, afterLines);
|
|
189
|
-
const lines = [
|
|
190
|
-
`--- a/implementation/${escapeDiffPath(relativePath)}`,
|
|
191
|
-
`+++ b/implementation/${escapeDiffPath(relativePath)}`,
|
|
192
|
-
`@@ -1,${beforeLines.length} +1,${afterLines.length} @@`
|
|
193
|
-
];
|
|
194
|
-
for (const change of changes) {
|
|
195
|
-
const prefix = change.type === "added" ? "+" : change.type === "removed" ? "-" : " ";
|
|
196
|
-
lines.push(`${prefix}${change.text}`);
|
|
197
|
-
}
|
|
198
|
-
return `${lines.join("\n")}\n`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* @param {string} filePath
|
|
203
|
-
* @returns {{ text: string|null, binary: boolean, omitted: boolean }}
|
|
204
|
-
*/
|
|
205
|
-
function readReviewText(filePath) {
|
|
206
|
-
const bytes = fs.readFileSync(filePath);
|
|
207
|
-
if (bytes.length > MAX_TEXT_DIFF_BYTES) {
|
|
208
|
-
return { text: null, binary: false, omitted: true };
|
|
209
|
-
}
|
|
210
|
-
if (!isLikelyText(bytes)) {
|
|
211
|
-
return { text: null, binary: true, omitted: false };
|
|
212
|
-
}
|
|
213
|
-
return { text: bytes.toString("utf8"), binary: false, omitted: false };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* @param {string} implementationRoot
|
|
218
|
-
* @param {string} currentDir
|
|
219
|
-
* @param {string[]} files
|
|
220
|
-
* @returns {void}
|
|
221
|
-
*/
|
|
222
|
-
function collectImplementationFiles(implementationRoot, currentDir, files) {
|
|
223
|
-
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
|
224
|
-
if (IGNORED_IMPLEMENTATION_ENTRIES.has(entry.name)) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
const entryPath = path.join(currentDir, entry.name);
|
|
228
|
-
const relativePath = normalizeRelativePath(path.relative(implementationRoot, entryPath));
|
|
229
|
-
if (entry.isSymbolicLink()) {
|
|
230
|
-
throw new Error(unsupportedImplementationSymlinkMessage(relativePath));
|
|
231
|
-
}
|
|
232
|
-
if (entry.isDirectory()) {
|
|
233
|
-
collectImplementationFiles(implementationRoot, entryPath, files);
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
if (entry.isFile()) {
|
|
237
|
-
files.push(relativePath);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* @param {string} configDir
|
|
244
|
-
* @returns {{ algorithm: "sha256", root: string, digest: string, files: Array<{ path: string, sha256: string, size: number }> }}
|
|
245
|
-
*/
|
|
246
|
-
export function hashImplementationContent(configDir) {
|
|
247
|
-
const implementationRoot = path.join(configDir, "implementation");
|
|
248
|
-
if (!fs.existsSync(implementationRoot) || !fs.statSync(implementationRoot).isDirectory()) {
|
|
249
|
-
throw new Error(`Cannot trust template implementation because ${normalizeRoot(implementationRoot)} does not exist.`);
|
|
250
|
-
}
|
|
251
|
-
/** @type {string[]} */
|
|
252
|
-
const relativePaths = [];
|
|
253
|
-
collectImplementationFiles(implementationRoot, implementationRoot, relativePaths);
|
|
254
|
-
relativePaths.sort((a, b) => a.localeCompare(b));
|
|
255
|
-
const files = relativePaths.map((relativePath) => {
|
|
256
|
-
const filePath = path.join(implementationRoot, relativePath);
|
|
257
|
-
const bytes = fs.readFileSync(filePath);
|
|
258
|
-
return {
|
|
259
|
-
path: relativePath,
|
|
260
|
-
sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
|
|
261
|
-
size: bytes.length
|
|
262
|
-
};
|
|
263
|
-
});
|
|
264
|
-
const aggregate = crypto.createHash("sha256");
|
|
265
|
-
for (const file of files) {
|
|
266
|
-
aggregate.update(file.path);
|
|
267
|
-
aggregate.update("\0");
|
|
268
|
-
aggregate.update(file.sha256);
|
|
269
|
-
aggregate.update("\0");
|
|
270
|
-
aggregate.update(String(file.size));
|
|
271
|
-
aggregate.update("\0");
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
algorithm: "sha256",
|
|
275
|
-
root: "implementation",
|
|
276
|
-
digest: aggregate.digest("hex"),
|
|
277
|
-
files
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* @param {Record<string, any>} config
|
|
283
|
-
* @returns {{ id: string|null, module: string, export: string }}
|
|
284
|
-
*/
|
|
285
|
-
export function implementationTrustFingerprint(config) {
|
|
286
|
-
const implementationModule = config.implementation_module || config.module;
|
|
287
|
-
if (!implementationModule || typeof implementationModule !== "string") {
|
|
288
|
-
throw new Error("Topogram implementation config is missing implementation module.");
|
|
289
|
-
}
|
|
290
|
-
return {
|
|
291
|
-
id: config.implementation_id || config.id || null,
|
|
292
|
-
module: implementationModule,
|
|
293
|
-
export: config.implementation_export || config.export || "default"
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* @param {{ config: Record<string, any>, configDir: string }} implementationInfo
|
|
299
|
-
* @param {Record<string, any>|null} [projectConfig]
|
|
300
|
-
* @returns {boolean}
|
|
301
|
-
*/
|
|
302
|
-
export function implementationRequiresTrust(implementationInfo, projectConfig = null) {
|
|
303
|
-
const fingerprint = implementationTrustFingerprint(implementationInfo.config);
|
|
304
|
-
const modulePath = path.resolve(implementationInfo.configDir, fingerprint.module);
|
|
305
|
-
const implementationRoot = path.resolve(implementationInfo.configDir, "implementation");
|
|
306
|
-
return isSameOrInside(implementationRoot, modulePath) || projectHasTemplateAttachment(projectConfig);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* @param {{ config: Record<string, any>, configDir: string }} implementationInfo
|
|
311
|
-
* @returns {boolean}
|
|
312
|
-
*/
|
|
313
|
-
function implementationModuleIsUnderRoot(implementationInfo) {
|
|
314
|
-
const fingerprint = implementationTrustFingerprint(implementationInfo.config);
|
|
315
|
-
const modulePath = path.resolve(implementationInfo.configDir, fingerprint.module);
|
|
316
|
-
const implementationRoot = path.resolve(implementationInfo.configDir, "implementation");
|
|
317
|
-
return isSameOrInside(implementationRoot, modulePath);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* @param {Record<string, any>|null} projectConfig
|
|
322
|
-
* @returns {boolean}
|
|
323
|
-
*/
|
|
324
|
-
function projectHasTemplateAttachment(projectConfig) {
|
|
325
|
-
const template = projectConfig?.template || null;
|
|
326
|
-
return Boolean(template?.id || template?.sourceSpec || template?.requested);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* @param {string} configDir
|
|
331
|
-
* @returns {TemplateTrustRecord|null}
|
|
332
|
-
*/
|
|
333
|
-
export function readTemplateTrustRecord(configDir) {
|
|
334
|
-
const trustPath = path.join(configDir, TEMPLATE_TRUST_FILE);
|
|
335
|
-
if (!fs.existsSync(trustPath)) {
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
return /** @type {TemplateTrustRecord} */ (JSON.parse(fs.readFileSync(trustPath, "utf8")));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* @param {string} configDir
|
|
343
|
-
* @param {Record<string, any>} projectConfig
|
|
344
|
-
* @param {{ id: string|null, module: string, export: string }} implementation
|
|
345
|
-
* @returns {TemplateTrustRecord}
|
|
346
|
-
*/
|
|
347
|
-
function buildTrustRecord(configDir, projectConfig, implementation) {
|
|
348
|
-
const template = projectConfig.template || {};
|
|
349
|
-
const content = hashImplementationContent(configDir);
|
|
350
|
-
return {
|
|
351
|
-
version: "1",
|
|
352
|
-
trustPolicy: TEMPLATE_TRUST_POLICY,
|
|
353
|
-
trustedAt: new Date().toISOString(),
|
|
354
|
-
template: {
|
|
355
|
-
id: typeof template.id === "string" ? template.id : null,
|
|
356
|
-
version: typeof template.version === "string" ? template.version : null,
|
|
357
|
-
source: typeof template.source === "string" ? template.source : null,
|
|
358
|
-
sourceSpec: typeof template.sourceSpec === "string" ? template.sourceSpec : null,
|
|
359
|
-
requested: typeof template.requested === "string" ? template.requested : null,
|
|
360
|
-
sourceRoot: typeof template.sourceRoot === "string" ? template.sourceRoot : null,
|
|
361
|
-
catalog: template.catalog && typeof template.catalog === "object" && !Array.isArray(template.catalog)
|
|
362
|
-
? template.catalog
|
|
363
|
-
: null
|
|
364
|
-
},
|
|
365
|
-
implementation,
|
|
366
|
-
content
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* @param {string} configDir
|
|
372
|
-
* @param {Record<string, any>} projectConfig
|
|
373
|
-
* @returns {TemplateTrustRecord}
|
|
374
|
-
*/
|
|
375
|
-
export function writeTemplateTrustRecord(configDir, projectConfig) {
|
|
376
|
-
const implementationConfig = projectConfig.implementation;
|
|
377
|
-
if (!implementationConfig) {
|
|
378
|
-
throw new Error("Cannot trust template implementation because topogram.project.json has no implementation config.");
|
|
379
|
-
}
|
|
380
|
-
const implementationInfo = {
|
|
381
|
-
config: implementationConfig,
|
|
382
|
-
configDir
|
|
383
|
-
};
|
|
384
|
-
if (!implementationModuleIsUnderRoot(implementationInfo)) {
|
|
385
|
-
const implementation = implementationTrustFingerprint(implementationConfig);
|
|
386
|
-
throw new Error(implementationOutsideRootMessage(implementation.module));
|
|
387
|
-
}
|
|
388
|
-
const implementation = implementationTrustFingerprint(implementationConfig);
|
|
389
|
-
const record = buildTrustRecord(configDir, projectConfig, implementation);
|
|
390
|
-
fs.writeFileSync(path.join(configDir, TEMPLATE_TRUST_FILE), `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
391
|
-
return record;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} implementationInfo
|
|
396
|
-
* @param {Record<string, any>|null} projectConfig
|
|
397
|
-
* @returns {void}
|
|
398
|
-
*/
|
|
399
|
-
export function assertTrustedImplementation(implementationInfo, projectConfig = null) {
|
|
400
|
-
const status = getTemplateTrustStatus(implementationInfo, projectConfig);
|
|
401
|
-
if (!status.requiresTrust || status.ok) {
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
const firstIssue = status.issues[0] || "implementation trust is invalid";
|
|
405
|
-
const guidance = templateTrustRecoveryGuidance(firstIssue);
|
|
406
|
-
throw new Error(
|
|
407
|
-
guidance ? `${firstIssue}. ${guidance}` : firstIssue
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* @param {Map<string, { path: string, sha256: string, size: number }>} trustedByPath
|
|
413
|
-
* @param {Map<string, { path: string, sha256: string, size: number }>} currentByPath
|
|
414
|
-
* @returns {{ added: string[], removed: string[], changed: string[] }}
|
|
415
|
-
*/
|
|
416
|
-
function diffContentFiles(trustedByPath, currentByPath) {
|
|
417
|
-
/** @type {string[]} */
|
|
418
|
-
const added = [];
|
|
419
|
-
/** @type {string[]} */
|
|
420
|
-
const removed = [];
|
|
421
|
-
/** @type {string[]} */
|
|
422
|
-
const changed = [];
|
|
423
|
-
for (const [filePath, current] of currentByPath) {
|
|
424
|
-
const trusted = trustedByPath.get(filePath);
|
|
425
|
-
if (!trusted) {
|
|
426
|
-
added.push(filePath);
|
|
427
|
-
} else if (trusted.sha256 !== current.sha256 || trusted.size !== current.size) {
|
|
428
|
-
changed.push(filePath);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
for (const filePath of trustedByPath.keys()) {
|
|
432
|
-
if (!currentByPath.has(filePath)) {
|
|
433
|
-
removed.push(filePath);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
added: added.sort((a, b) => a.localeCompare(b)),
|
|
438
|
-
removed: removed.sort((a, b) => a.localeCompare(b)),
|
|
439
|
-
changed: changed.sort((a, b) => a.localeCompare(b))
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* @param {string} configDir
|
|
445
|
-
* @param {string} relativePath
|
|
446
|
-
* @param {{ path: string, sha256: string, size: number }|null} file
|
|
447
|
-
* @returns {{ path: string, sha256: string|null, size: number|null, binary: boolean, diffOmitted: boolean, text: string|null }}
|
|
448
|
-
*/
|
|
449
|
-
function implementationReviewFile(configDir, relativePath, file) {
|
|
450
|
-
if (!file) {
|
|
451
|
-
return { path: relativePath, sha256: null, size: null, binary: false, diffOmitted: false, text: null };
|
|
452
|
-
}
|
|
453
|
-
const reviewText = readReviewText(path.join(configDir, "implementation", relativePath));
|
|
454
|
-
return {
|
|
455
|
-
path: relativePath,
|
|
456
|
-
sha256: file.sha256,
|
|
457
|
-
size: file.size,
|
|
458
|
-
binary: reviewText.binary,
|
|
459
|
-
diffOmitted: reviewText.omitted,
|
|
460
|
-
text: reviewText.text
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} implementationInfo
|
|
466
|
-
* @param {Record<string, any>|null} projectConfig
|
|
467
|
-
* @returns {{ ok: boolean, requiresTrust: boolean, trustPath: string, trustRecord: TemplateTrustRecord|null, template: { id: string|null, version: string|null, source: string|null, sourceSpec: string|null, requested: string|null, sourceRoot: string|null, catalog?: Record<string, any>|null, includesExecutableImplementation: boolean|null }, implementation: { id: string|null, module: string|null, export: string|null }, content: { trustedDigest: string|null, currentDigest: string|null, added: string[], removed: string[], changed: string[] }, issues: string[] }}
|
|
468
|
-
*/
|
|
469
|
-
export function getTemplateTrustStatus(implementationInfo, projectConfig = null) {
|
|
470
|
-
const templateAttached = projectHasTemplateAttachment(projectConfig);
|
|
471
|
-
if (!implementationRequiresTrust(implementationInfo, projectConfig)) {
|
|
472
|
-
return {
|
|
473
|
-
ok: true,
|
|
474
|
-
requiresTrust: false,
|
|
475
|
-
trustPath: path.join(implementationInfo.configDir, TEMPLATE_TRUST_FILE),
|
|
476
|
-
trustRecord: null,
|
|
477
|
-
template: { id: null, version: null, source: null, sourceSpec: null, requested: null, sourceRoot: null, catalog: null, includesExecutableImplementation: null },
|
|
478
|
-
implementation: { id: null, module: null, export: null },
|
|
479
|
-
content: { trustedDigest: null, currentDigest: null, added: [], removed: [], changed: [] },
|
|
480
|
-
issues: []
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
const fingerprint = implementationTrustFingerprint(implementationInfo.config);
|
|
484
|
-
const moduleInsideImplementation = implementationModuleIsUnderRoot(implementationInfo);
|
|
485
|
-
const trustRecord = readTemplateTrustRecord(implementationInfo.configDir);
|
|
486
|
-
const configLabel = implementationInfo.configPath || "topogram.project.json";
|
|
487
|
-
const trustPath = path.join(implementationInfo.configDir, TEMPLATE_TRUST_FILE);
|
|
488
|
-
const projectTemplate = projectConfig?.template || null;
|
|
489
|
-
/** @type {string[]} */
|
|
490
|
-
const issues = [];
|
|
491
|
-
/** @type {{ trustedDigest: string|null, currentDigest: string|null, added: string[], removed: string[], changed: string[] }} */
|
|
492
|
-
const contentStatus = { trustedDigest: null, currentDigest: null, added: [], removed: [], changed: [] };
|
|
493
|
-
|
|
494
|
-
if (templateAttached && !moduleInsideImplementation) {
|
|
495
|
-
issues.push(implementationOutsideRootMessage(fingerprint.module));
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (!trustRecord) {
|
|
499
|
-
issues.push(
|
|
500
|
-
`Refusing to load executable implementation '${fingerprint.module}' from ${normalizeRoot(configLabel)} without ${TEMPLATE_TRUST_FILE}`
|
|
501
|
-
);
|
|
502
|
-
} else {
|
|
503
|
-
if (trustRecord.trustPolicy !== TEMPLATE_TRUST_POLICY) {
|
|
504
|
-
issues.push(`${TEMPLATE_TRUST_FILE} uses unsupported trust policy '${trustRecord.trustPolicy}'`);
|
|
505
|
-
}
|
|
506
|
-
if (trustRecord.implementation?.module !== fingerprint.module) {
|
|
507
|
-
issues.push(`${TEMPLATE_TRUST_FILE} trusts implementation module '${trustRecord.implementation?.module}', but ${normalizeRoot(configLabel)} uses '${fingerprint.module}'`);
|
|
508
|
-
}
|
|
509
|
-
if (trustRecord.implementation?.export !== fingerprint.export) {
|
|
510
|
-
issues.push(`${TEMPLATE_TRUST_FILE} trusts implementation export '${trustRecord.implementation?.export}', but ${normalizeRoot(configLabel)} uses '${fingerprint.export}'`);
|
|
511
|
-
}
|
|
512
|
-
const trustedId = trustRecord.implementation?.id || null;
|
|
513
|
-
if (trustedId !== (fingerprint.id || null)) {
|
|
514
|
-
issues.push(`${TEMPLATE_TRUST_FILE} trusts implementation id '${trustedId || ""}', but ${normalizeRoot(configLabel)} uses '${fingerprint.id || ""}'`);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (projectTemplate?.id && trustRecord.template?.id !== projectTemplate.id) {
|
|
518
|
-
issues.push(`${TEMPLATE_TRUST_FILE} trusts template '${trustRecord.template?.id}', but topogram.project.json declares '${projectTemplate.id}'`);
|
|
519
|
-
}
|
|
520
|
-
if (projectTemplate?.version && trustRecord.template?.version !== projectTemplate.version) {
|
|
521
|
-
issues.push(`${TEMPLATE_TRUST_FILE} trusts template version '${trustRecord.template?.version}', but topogram.project.json declares '${projectTemplate.version}'`);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (!moduleInsideImplementation) {
|
|
525
|
-
// The module itself is outside the only supported trust root. Do not
|
|
526
|
-
// pretend the implementation/ content digest covers the executable code.
|
|
527
|
-
} else if (!trustRecord.content) {
|
|
528
|
-
issues.push(`${TEMPLATE_TRUST_FILE} is missing implementation content hashes`);
|
|
529
|
-
} else if (trustRecord.content.algorithm !== "sha256") {
|
|
530
|
-
issues.push(`${TEMPLATE_TRUST_FILE} uses unsupported content hash algorithm '${trustRecord.content.algorithm}'`);
|
|
531
|
-
} else {
|
|
532
|
-
try {
|
|
533
|
-
const currentContent = hashImplementationContent(implementationInfo.configDir);
|
|
534
|
-
contentStatus.trustedDigest = trustRecord.content.digest;
|
|
535
|
-
contentStatus.currentDigest = currentContent.digest;
|
|
536
|
-
const trustedByPath = new Map((trustRecord.content.files || []).map((file) => [file.path, file]));
|
|
537
|
-
const currentByPath = new Map(currentContent.files.map((file) => [file.path, file]));
|
|
538
|
-
const diff = diffContentFiles(trustedByPath, currentByPath);
|
|
539
|
-
contentStatus.added = diff.added;
|
|
540
|
-
contentStatus.removed = diff.removed;
|
|
541
|
-
contentStatus.changed = diff.changed;
|
|
542
|
-
if (trustRecord.content.digest !== currentContent.digest) {
|
|
543
|
-
issues.push(`${TEMPLATE_TRUST_FILE} implementation content changed since it was last trusted`);
|
|
544
|
-
}
|
|
545
|
-
} catch (error) {
|
|
546
|
-
issues.push(error instanceof Error ? error.message : String(error));
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return {
|
|
552
|
-
ok: issues.length === 0,
|
|
553
|
-
requiresTrust: true,
|
|
554
|
-
trustPath,
|
|
555
|
-
trustRecord,
|
|
556
|
-
template: {
|
|
557
|
-
id: projectTemplate?.id || trustRecord?.template?.id || null,
|
|
558
|
-
version: projectTemplate?.version || trustRecord?.template?.version || null,
|
|
559
|
-
source: projectTemplate?.source || trustRecord?.template?.source || null,
|
|
560
|
-
sourceSpec: projectTemplate?.sourceSpec || trustRecord?.template?.sourceSpec || null,
|
|
561
|
-
requested: projectTemplate?.requested || trustRecord?.template?.requested || null,
|
|
562
|
-
sourceRoot: projectTemplate?.sourceRoot || trustRecord?.template?.sourceRoot || null,
|
|
563
|
-
catalog: projectTemplate?.catalog || trustRecord?.template?.catalog || null,
|
|
564
|
-
includesExecutableImplementation: typeof projectTemplate?.includesExecutableImplementation === "boolean"
|
|
565
|
-
? projectTemplate.includesExecutableImplementation
|
|
566
|
-
: null
|
|
567
|
-
},
|
|
568
|
-
implementation: fingerprint,
|
|
569
|
-
content: contentStatus,
|
|
570
|
-
issues
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* @param {{ config: Record<string, any>, configPath: string|null, configDir: string }} implementationInfo
|
|
576
|
-
* @param {Record<string, any>|null} projectConfig
|
|
577
|
-
* @returns {{ ok: boolean, requiresTrust: boolean, status: ReturnType<typeof getTemplateTrustStatus>, files: Array<{ path: string, kind: "added"|"removed"|"changed", trusted: { path: string, sha256: string|null, size: number|null }|null, current: { path: string, sha256: string|null, size: number|null, binary: boolean, diffOmitted: boolean }|null, binary: boolean, diffOmitted: boolean, unifiedDiff: string|null }> }}
|
|
578
|
-
*/
|
|
579
|
-
export function getTemplateTrustDiff(implementationInfo, projectConfig = null) {
|
|
580
|
-
const status = getTemplateTrustStatus(implementationInfo, projectConfig);
|
|
581
|
-
if (!status.requiresTrust || !status.trustRecord?.content) {
|
|
582
|
-
return { ok: status.ok, requiresTrust: status.requiresTrust, status, files: [] };
|
|
583
|
-
}
|
|
584
|
-
let currentContent;
|
|
585
|
-
try {
|
|
586
|
-
currentContent = hashImplementationContent(implementationInfo.configDir);
|
|
587
|
-
} catch (_error) {
|
|
588
|
-
return { ok: false, requiresTrust: status.requiresTrust, status, files: [] };
|
|
589
|
-
}
|
|
590
|
-
const trustedByPath = new Map((status.trustRecord.content.files || []).map((file) => [file.path, file]));
|
|
591
|
-
const currentByPath = new Map(currentContent.files.map((file) => [file.path, file]));
|
|
592
|
-
/** @type {Array<{ path: string, kind: "added"|"removed"|"changed", trusted: { path: string, sha256: string|null, size: number|null }|null, current: { path: string, sha256: string|null, size: number|null, binary: boolean, diffOmitted: boolean }|null, binary: boolean, diffOmitted: boolean, unifiedDiff: string|null }>} */
|
|
593
|
-
const files = [];
|
|
594
|
-
|
|
595
|
-
for (const relativePath of status.content.changed) {
|
|
596
|
-
const trusted = trustedByPath.get(relativePath) || null;
|
|
597
|
-
const current = currentByPath.get(relativePath) || null;
|
|
598
|
-
const currentReview = implementationReviewFile(implementationInfo.configDir, relativePath, current);
|
|
599
|
-
files.push({
|
|
600
|
-
path: relativePath,
|
|
601
|
-
kind: "changed",
|
|
602
|
-
trusted: trusted ? { path: relativePath, sha256: trusted.sha256, size: trusted.size } : null,
|
|
603
|
-
current: {
|
|
604
|
-
path: relativePath,
|
|
605
|
-
sha256: currentReview.sha256,
|
|
606
|
-
size: currentReview.size,
|
|
607
|
-
binary: currentReview.binary,
|
|
608
|
-
diffOmitted: currentReview.diffOmitted
|
|
609
|
-
},
|
|
610
|
-
binary: currentReview.binary,
|
|
611
|
-
diffOmitted: true,
|
|
612
|
-
unifiedDiff: null
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
for (const relativePath of status.content.added) {
|
|
616
|
-
const current = currentByPath.get(relativePath) || null;
|
|
617
|
-
const currentReview = implementationReviewFile(implementationInfo.configDir, relativePath, current);
|
|
618
|
-
files.push({
|
|
619
|
-
path: relativePath,
|
|
620
|
-
kind: "added",
|
|
621
|
-
trusted: null,
|
|
622
|
-
current: {
|
|
623
|
-
path: relativePath,
|
|
624
|
-
sha256: currentReview.sha256,
|
|
625
|
-
size: currentReview.size,
|
|
626
|
-
binary: currentReview.binary,
|
|
627
|
-
diffOmitted: currentReview.diffOmitted
|
|
628
|
-
},
|
|
629
|
-
binary: currentReview.binary,
|
|
630
|
-
diffOmitted: currentReview.binary || currentReview.diffOmitted,
|
|
631
|
-
unifiedDiff: currentReview.binary || currentReview.diffOmitted
|
|
632
|
-
? null
|
|
633
|
-
: unifiedTextDiff(relativePath, null, currentReview.text)
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
for (const relativePath of status.content.removed) {
|
|
637
|
-
const trusted = trustedByPath.get(relativePath) || null;
|
|
638
|
-
files.push({
|
|
639
|
-
path: relativePath,
|
|
640
|
-
kind: "removed",
|
|
641
|
-
trusted: trusted ? { path: relativePath, sha256: trusted.sha256, size: trusted.size } : null,
|
|
642
|
-
current: null,
|
|
643
|
-
binary: false,
|
|
644
|
-
diffOmitted: true,
|
|
645
|
-
unifiedDiff: null
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
files.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));
|
|
650
|
-
return {
|
|
651
|
-
ok: status.ok,
|
|
652
|
-
requiresTrust: status.requiresTrust,
|
|
653
|
-
status,
|
|
654
|
-
files
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* @param {{ config: Record<string, any>, configPath: string|null, configDir: string }|null} projectConfigInfo
|
|
660
|
-
* @returns {{ ok: boolean, errors: Array<{ message: string, loc: any }> }}
|
|
661
|
-
*/
|
|
662
|
-
export function validateProjectImplementationTrust(projectConfigInfo) {
|
|
663
|
-
if (!projectConfigInfo?.config?.implementation) {
|
|
664
|
-
return { ok: true, errors: [] };
|
|
665
|
-
}
|
|
666
|
-
const implementationModule =
|
|
667
|
-
projectConfigInfo.config.implementation.implementation_module ||
|
|
668
|
-
projectConfigInfo.config.implementation.module;
|
|
669
|
-
if (!implementationModule) {
|
|
670
|
-
return { ok: true, errors: [] };
|
|
671
|
-
}
|
|
672
|
-
const implementationInfo = {
|
|
673
|
-
config: projectConfigInfo.config.implementation,
|
|
674
|
-
configPath: projectConfigInfo.configPath,
|
|
675
|
-
configDir: projectConfigInfo.configDir
|
|
676
|
-
};
|
|
677
|
-
try {
|
|
678
|
-
assertTrustedImplementation(implementationInfo, projectConfigInfo.config);
|
|
679
|
-
return { ok: true, errors: [] };
|
|
680
|
-
} catch (error) {
|
|
681
|
-
return {
|
|
682
|
-
ok: false,
|
|
683
|
-
errors: [{
|
|
684
|
-
message: error instanceof Error ? error.message : String(error),
|
|
685
|
-
loc: null
|
|
686
|
-
}]
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
}
|
|
3
|
+
export {
|
|
4
|
+
TEMPLATE_TRUST_FILE,
|
|
5
|
+
TEMPLATE_TRUST_POLICY,
|
|
6
|
+
templateTrustRecoveryGuidance
|
|
7
|
+
} from "./template-trust/constants.js";
|
|
8
|
+
export {
|
|
9
|
+
hashImplementationContent
|
|
10
|
+
} from "./template-trust/content.js";
|
|
11
|
+
export {
|
|
12
|
+
implementationRequiresTrust,
|
|
13
|
+
implementationTrustFingerprint
|
|
14
|
+
} from "./template-trust/policy.js";
|
|
15
|
+
export {
|
|
16
|
+
readTemplateTrustRecord,
|
|
17
|
+
writeTemplateTrustRecord
|
|
18
|
+
} from "./template-trust/record.js";
|
|
19
|
+
export {
|
|
20
|
+
assertTrustedImplementation,
|
|
21
|
+
getTemplateTrustStatus,
|
|
22
|
+
validateProjectImplementationTrust
|
|
23
|
+
} from "./template-trust/status.js";
|
|
24
|
+
export {
|
|
25
|
+
getTemplateTrustDiff
|
|
26
|
+
} from "./template-trust/diff.js";
|