mirai-graph 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +3 -0
- package/CHANGELOG.md +58 -0
- package/CITATION.cff +2 -2
- package/README.md +29 -3
- package/docs/adoption/cli.md +13 -0
- package/package.json +40 -38
- package/packages/cli/context-pack.js +0 -0
- package/packages/cli/graph-manifest.js +2 -1
- package/packages/cli/mirai-graph.js +1 -1
- package/packages/cli/mirai_graph.js +0 -0
- package/packages/cli/project-technology.js +27 -2
- package/packages/cli/readiness-score.js +0 -0
- package/packages/cli/seed-preview.js +0 -0
- package/packages/cli/validate-artifact-releases.js +282 -0
- package/packages/cli/validate-mirai-graph.js +0 -0
- package/packages/cli/validate-profile-results.js +0 -0
- package/packages/cli/validate-project-continuity.js +173 -0
- package/packages/cli/validate-project-technology.js +26 -2
- package/packages/project-technology/artifact-release.js +738 -0
- package/packages/project-technology/context-traversal.js +22 -6
- package/packages/project-technology/continuity.js +414 -0
- package/packages/project-technology/index.js +146 -36
- package/profiles/implementation-control/profile.json +3 -1
- package/profiles/project-management/profile.json +8 -1
- package/releases/1.2.0.md +19 -0
- package/releases/1.3.0.md +16 -0
- package/releases/README.md +3 -0
- package/schemas/project-technology-extension.schema.json +2 -1
- package/standard/context-pack-generation.md +8 -2
- package/standard/object-model.md +6 -0
- package/standard/project-technology.md +79 -4
- package/standard/test-suite.md +4 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const assert = require("assert");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const { spawnSync } = require("child_process");
|
|
10
|
+
const technology = require("../project-technology");
|
|
11
|
+
|
|
12
|
+
function writeJson(file, value) {
|
|
13
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
14
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function git(repo, ...args) {
|
|
18
|
+
const run = spawnSync("git", args, { cwd: repo, encoding: "utf8" });
|
|
19
|
+
if (run.status !== 0) throw new Error(run.stderr || `git ${args.join(" ")} failed`);
|
|
20
|
+
return run.stdout.trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function manifest(id) {
|
|
24
|
+
return {
|
|
25
|
+
$schema: "https://mirai-graph.dev/schemas/graph-manifest.schema.json",
|
|
26
|
+
format: "mirai-graph", schema_version: "2.0.0", id, aliases: [], title: id,
|
|
27
|
+
scope: "repository", kind: "repository_graph", owner: "fixture-owner",
|
|
28
|
+
profiles: ["project_management", "implementation_control"],
|
|
29
|
+
graph: {
|
|
30
|
+
root: "graph", source_of_truth: ["graph/specs"], objects: ["graph/specs/objects.json"],
|
|
31
|
+
relations: ["graph/specs/relations.json"], schemas: [], generated: ["graph/generated"], raw_sources: ["README.md"],
|
|
32
|
+
},
|
|
33
|
+
imports: [],
|
|
34
|
+
extensions: { "mirai.project_technology": technology.extensionContract() },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createRepo(root, name, withGit = true) {
|
|
39
|
+
const repo = path.join(root, name); fs.mkdirSync(repo, { recursive: true });
|
|
40
|
+
writeJson(path.join(repo, "graph.json"), manifest(name));
|
|
41
|
+
writeJson(path.join(repo, "graph/specs/objects.json"), [{
|
|
42
|
+
id: "goal.fixture.delivery", kind: "goal", title: "Deliver fixture", summary: "Complete a safe fixture task",
|
|
43
|
+
readiness: "accepted", profile: "project_management", source_refs: ["README.md"],
|
|
44
|
+
}]);
|
|
45
|
+
writeJson(path.join(repo, "graph/specs/relations.json"), []);
|
|
46
|
+
fs.writeFileSync(path.join(repo, "README.md"), "# Fixture\nPortable safe context.\n");
|
|
47
|
+
if (withGit) {
|
|
48
|
+
git(repo, "init", "-q"); git(repo, "config", "user.name", "Mirai Fixture"); git(repo, "config", "user.email", "fixture@example.invalid");
|
|
49
|
+
git(repo, "add", "."); git(repo, "commit", "-qm", "initial graph");
|
|
50
|
+
}
|
|
51
|
+
return repo;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function evidence(task, options = {}) {
|
|
55
|
+
return {
|
|
56
|
+
task_digest: technology.sha256(task, true),
|
|
57
|
+
outcome: options.outcome || `Verified result for ${task}`,
|
|
58
|
+
requirement_refs: options.requirement_refs || ["requirement.fixture.portability"],
|
|
59
|
+
evidence_refs: ["repo://fixture/checks/continuity"],
|
|
60
|
+
checks: [{ id: "check.fixture.continuity", verdict: "pass", evidence_ref: "repo://fixture/checks/continuity" }],
|
|
61
|
+
changed_surfaces: ["src/fixture.js"],
|
|
62
|
+
case_signature: options.case_signature || "case.fixture.portable-work",
|
|
63
|
+
method_candidate: options.method_candidate || null,
|
|
64
|
+
decisions: options.decisions || [],
|
|
65
|
+
source_revision: options.source_revision || null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const checks = [];
|
|
70
|
+
function check(id, condition, details = null) {
|
|
71
|
+
checks.push({ id, passed: Boolean(condition), details });
|
|
72
|
+
assert.ok(condition, `${id}: ${JSON.stringify(details)}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "mirai-continuity-"));
|
|
76
|
+
const stateRoot = path.join(root, "host-state");
|
|
77
|
+
try {
|
|
78
|
+
const repo = createRepo(root, "git-project");
|
|
79
|
+
const before = git(repo, "status", "--porcelain=v1");
|
|
80
|
+
const taskStart = technology.execute("sync", repo, { apply: true, boundary: "task_start", stateRoot });
|
|
81
|
+
check("task_start_is_read_only", taskStart.status === "success" && taskStart.changed === false && git(repo, "status", "--porcelain=v1") === before, taskStart);
|
|
82
|
+
const preview = technology.execute("sync", repo, { apply: false, boundary: "task_complete", continuityEvidence: evidence("preview"), stateRoot });
|
|
83
|
+
check("preview_is_zero_write", preview.status === "preview" && git(repo, "status", "--porcelain=v1") === before, preview);
|
|
84
|
+
const missingReceiptVerify = technology.verify(repo, { stateRoot });
|
|
85
|
+
check("configured_continuity_without_receipt_blocks_verify", missingReceiptVerify.status === "blocked" && missingReceiptVerify.blockers.includes("continuity_missing"), missingReceiptVerify);
|
|
86
|
+
|
|
87
|
+
const first = technology.execute("sync", repo, { apply: true, boundary: "stage_complete", continuityEvidence: evidence("first", { method_candidate: "Use the verified bounded fixture path." }), stateRoot });
|
|
88
|
+
check("verified_result_is_saved", first.status === "success" && first.changed && first.continuity.saved_refs.length === 2, first);
|
|
89
|
+
check("portable_authority_is_graph_specs", fs.existsSync(path.join(repo, "graph/specs/project-continuity.json")) && first.continuity.authority === "graph/specs");
|
|
90
|
+
check("host_state_is_outside_project", !fs.existsSync(path.join(repo, ".mirai-graph/project-technology/receipts")) && fs.existsSync(stateRoot));
|
|
91
|
+
check("human_projection_is_generated", fs.readFileSync(path.join(repo, "graph/docs/project-context.md"), "utf8").includes("not a source of truth"));
|
|
92
|
+
const objectsAfterFirst = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")).objects;
|
|
93
|
+
check("first_method_is_proposal", objectsAfterFirst.some((item) => item.kind === "lesson" && item.readiness === "proposal"));
|
|
94
|
+
check("architecture_decision_is_not_accepted", technology.execute("sync", repo, {
|
|
95
|
+
apply: true, boundary: "task_complete", stateRoot,
|
|
96
|
+
continuityEvidence: evidence("decision", { decisions: [{ summary: "Replace the project architecture", changes_architecture: true }] }),
|
|
97
|
+
}).status === "success");
|
|
98
|
+
const decisionObjects = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")).objects;
|
|
99
|
+
check("architecture_change_remains_proposal", decisionObjects.some((item) => item.kind === "decision" && item.summary === "Replace the project architecture" && item.readiness === "proposal"));
|
|
100
|
+
|
|
101
|
+
const second = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("second", { method_candidate: "Use the verified bounded fixture path." }), stateRoot });
|
|
102
|
+
check("second_independent_case_is_saved", second.status === "success", second);
|
|
103
|
+
const objectsAfterSecond = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")).objects;
|
|
104
|
+
check("method_promotes_after_two_independent_cases", objectsAfterSecond.some((item) => item.kind === "lesson" && item.readiness === "accepted" && item.supporting_case_ids.length >= 2));
|
|
105
|
+
|
|
106
|
+
const beforeRepeat = fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"));
|
|
107
|
+
const repeated = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("second", { method_candidate: "Use the verified bounded fixture path." }), stateRoot });
|
|
108
|
+
check("repeat_is_byte_identical", repeated.status === "success" && repeated.changed === false && fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json")).equals(beforeRepeat), repeated);
|
|
109
|
+
check("fresh_continuity_verifies_without_target_for_light_work", technology.verify(repo, { stateRoot }).status === "success");
|
|
110
|
+
check("fresh_receipt_allows_significant_work", technology.continuity.verify(repo, JSON.parse(fs.readFileSync(path.join(repo, "graph.json"), "utf8")), { stateRoot }).status === "success");
|
|
111
|
+
|
|
112
|
+
const staleDigest = technology.continuity.graphDigest(repo);
|
|
113
|
+
fs.appendFileSync(path.join(repo, "graph/specs/objects.json"), " ");
|
|
114
|
+
check("stale_continuity_blocks_significant_work", technology.continuity.verify(repo, JSON.parse(fs.readFileSync(path.join(repo, "graph.json"), "utf8")), { stateRoot }).blockers.includes("continuity_stale"));
|
|
115
|
+
const baseObjects = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/objects.json"), "utf8"));
|
|
116
|
+
baseObjects[0].summary = "Changed concurrently";
|
|
117
|
+
fs.writeFileSync(path.join(repo, "graph/specs/objects.json"), JSON.stringify(baseObjects, null, 2) + "\n");
|
|
118
|
+
const cas = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("cas"), expectedGraphDigest: staleDigest, stateRoot });
|
|
119
|
+
check("compare_and_swap_conflict_is_safe", cas.status === "blocked" && cas.blockers.includes("continuity_compare_and_swap_conflict"), cas);
|
|
120
|
+
baseObjects[0].summary = "Complete a safe fixture task";
|
|
121
|
+
fs.writeFileSync(path.join(repo, "graph/specs/objects.json"), JSON.stringify(baseObjects, null, 2) + "\n");
|
|
122
|
+
|
|
123
|
+
const secret = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("secret", { outcome: "token=not-a-real-secret-value-123456" }), stateRoot });
|
|
124
|
+
check("secret_is_rejected", secret.status === "blocked" && secret.blockers.includes("continuity_sensitive_or_host_data_forbidden"), secret);
|
|
125
|
+
const privatePath = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: { ...evidence("private"), changed_surfaces: ["source/memory/private.md"] }, stateRoot });
|
|
126
|
+
check("private_projection_is_rejected", privatePath.status === "blocked" && privatePath.blockers.includes("continuity_sensitive_or_host_data_forbidden"), privatePath);
|
|
127
|
+
const failedCheck = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: { ...evidence("fail"), checks: [{ id: "check.fixture", verdict: "fail", evidence_ref: "repo://fixture/fail" }] }, stateRoot });
|
|
128
|
+
check("unverified_result_is_rejected", failedCheck.status === "blocked" && failedCheck.blockers.includes("continuity_result_not_verified"), failedCheck);
|
|
129
|
+
|
|
130
|
+
git(repo, "add", "graph.json", "graph/specs", "graph/docs"); git(repo, "commit", "-qm", "portable continuity records");
|
|
131
|
+
const discovered = technology.context(repo, { phase: "discover", task: "use the verified portable work case" });
|
|
132
|
+
check("new_task_discovers_past_case", discovered.status === "success" && discovered.traversal_receipt.candidates.some((item) => item.id.startsWith("regression_case.continuity.")), discovered.blockers);
|
|
133
|
+
const beforeReadOnly = git(repo, "status", "--porcelain=v1");
|
|
134
|
+
technology.status(repo); technology.context(repo, { phase: "discover", task: "read only project context" });
|
|
135
|
+
check("read_only_operations_write_nothing", git(repo, "status", "--porcelain=v1") === beforeReadOnly);
|
|
136
|
+
|
|
137
|
+
const folder = createRepo(root, "shared-folder", false);
|
|
138
|
+
const folderFirst = technology.execute("sync", folder, { apply: true, boundary: "task_complete", continuityEvidence: evidence("folder"), stateRoot });
|
|
139
|
+
check("non_git_folder_supports_continuity", folderFirst.status === "success" && folderFirst.changed, folderFirst);
|
|
140
|
+
const folderRepeat = technology.execute("sync", folder, { apply: true, boundary: "task_complete", continuityEvidence: evidence("folder"), stateRoot });
|
|
141
|
+
check("non_git_repeat_is_idempotent", folderRepeat.status === "success" && folderRepeat.changed === false, folderRepeat);
|
|
142
|
+
const sharedLease = path.join(folder, "graph/.project-technology-continuity.lock");
|
|
143
|
+
fs.writeFileSync(sharedLease, "occupied\n");
|
|
144
|
+
const leaseConflict = technology.execute("sync", folder, { apply: true, boundary: "task_complete", continuityEvidence: evidence("parallel"), stateRoot: path.join(root, "third-installation-state") });
|
|
145
|
+
check("shared_folder_lease_blocks_parallel_writer", leaseConflict.status === "blocked" && leaseConflict.blockers.includes("continuity_lease_conflict"), leaseConflict);
|
|
146
|
+
fs.unlinkSync(sharedLease);
|
|
147
|
+
const folderDiscovery = technology.context(folder, { phase: "discover", task: "portable fixture case" });
|
|
148
|
+
check("non_git_context_is_available", folderDiscovery.status === "success", folderDiscovery.blockers);
|
|
149
|
+
check("same_content_has_stable_digest", technology.continuity.graphDigest(folder) === technology.continuity.graphDigest(folder));
|
|
150
|
+
|
|
151
|
+
const otherStateRoot = path.join(root, "other-installation-state");
|
|
152
|
+
const firstInstallContext = technology.context(repo, { phase: "discover", task: "use verified portable work" });
|
|
153
|
+
const secondInstallContext = technology.context(repo, { phase: "discover", task: "use verified portable work", stateRoot: otherStateRoot });
|
|
154
|
+
check("two_installations_get_same_context", firstInstallContext.traversal_receipt.traversal_digest === secondInstallContext.traversal_receipt.traversal_digest);
|
|
155
|
+
check("portable_specs_contain_no_host_paths", !/(?:\/Users\/|\/home\/|[A-Za-z]:\\Users\\)/.test(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")));
|
|
156
|
+
check("continuity_policy_keeps_contract_version", JSON.parse(fs.readFileSync(path.join(repo, "graph.json"), "utf8")).extensions["mirai.project_technology"].contract_version === "1.0.0");
|
|
157
|
+
|
|
158
|
+
const bootstrapRepo = path.join(root, "existing-code-new-graph");
|
|
159
|
+
fs.mkdirSync(bootstrapRepo, { recursive: true });
|
|
160
|
+
fs.writeFileSync(path.join(bootstrapRepo, "README.md"), "# Existing code\n");
|
|
161
|
+
git(bootstrapRepo, "init", "-q"); git(bootstrapRepo, "config", "user.name", "Mirai Fixture"); git(bootstrapRepo, "config", "user.email", "fixture@example.invalid");
|
|
162
|
+
git(bootstrapRepo, "add", "README.md"); git(bootstrapRepo, "commit", "-qm", "existing code");
|
|
163
|
+
writeJson(path.join(bootstrapRepo, "graph.json"), manifest("bootstrap-project"));
|
|
164
|
+
writeJson(path.join(bootstrapRepo, "graph/specs/objects.json"), [{ id: "goal.bootstrap", kind: "goal", title: "Bootstrap", summary: "Use content-bound bootstrap context", readiness: "accepted", profile: "project_management" }]);
|
|
165
|
+
writeJson(path.join(bootstrapRepo, "graph/specs/relations.json"), []);
|
|
166
|
+
const bootstrapContext = technology.context(bootstrapRepo, { phase: "discover", task: "bootstrap context" });
|
|
167
|
+
check("new_git_graph_is_content_bound_before_first_commit", bootstrapContext.status === "success" && String(bootstrapContext.traversal_receipt.graph.revision).startsWith("content:"), bootstrapContext.blockers);
|
|
168
|
+
|
|
169
|
+
process.stdout.write(`${JSON.stringify({ status: "success", checks_passed: checks.length, checks_failed: 0, checks }, null, 2)}\n`);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
process.stdout.write(`${JSON.stringify({ status: "fail", checks_passed: checks.filter((item) => item.passed).length, checks_failed: 1, checks, error: error.message }, null, 2)}\n`);
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
}
|
|
@@ -72,6 +72,18 @@ function executionContract() {
|
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function continuityEvidence(repositoryId) {
|
|
76
|
+
return {
|
|
77
|
+
task_digest: technology.sha256(`task:${repositoryId}`, true),
|
|
78
|
+
outcome: `Exact target binding verified for ${repositoryId}`,
|
|
79
|
+
requirement_refs: ["requirement.fixture.runtime"],
|
|
80
|
+
evidence_refs: ["repo://fixture/evidence/project-technology"],
|
|
81
|
+
checks: [{ id: "check.fixture.binding", verdict: "pass", evidence_ref: "repo://fixture/evidence/project-technology" }],
|
|
82
|
+
changed_surfaces: ["graph/specs/project-continuity.json"],
|
|
83
|
+
case_signature: "case.fixture.target-binding",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
function initRepo(root, name, scope = "repository", profiles) {
|
|
76
88
|
const repo = path.join(root, name); fs.mkdirSync(repo, { recursive: true });
|
|
77
89
|
git(repo, "init", "-q"); git(repo, "config", "user.name", "Mirai Fixture");
|
|
@@ -104,6 +116,7 @@ function check(id, condition, details = null) {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "mirai-project-technology-test-"));
|
|
119
|
+
process.env.MIRAI_GRAPH_STATE_ROOT = path.join(root, "host-state");
|
|
107
120
|
try {
|
|
108
121
|
const provider = initRepo(root, "fixture-provider", "repository");
|
|
109
122
|
const consumer = initRepo(root, "fixture-consumer", "repository");
|
|
@@ -112,6 +125,11 @@ try {
|
|
|
112
125
|
const federation = initRepo(root, "fixture-federation", "federation", ["project_management", "implementation_control"]);
|
|
113
126
|
const providerRevision = addTarget(provider);
|
|
114
127
|
|
|
128
|
+
const legacyRuntimeRepo = initRepo(root, "legacy-runtime-repository");
|
|
129
|
+
writeJson(path.join(legacyRuntimeRepo, ".mirai-graph/project-technology/inventory.json"), { schema_version: "1.0.0", repository_id: "legacy-runtime-repository", inventory_digest: `sha256:${"0".repeat(64)}`, files: [] });
|
|
130
|
+
const legacyRuntimeEnabled = technology.execute("enable", legacyRuntimeRepo, { apply: true });
|
|
131
|
+
check("project_local_runtime_moves_to_host_state", legacyRuntimeEnabled.status === "success" && legacyRuntimeEnabled.migration_ref.startsWith("host-local://") && fs.existsSync(path.join(legacyRuntimeRepo, ".mirai-graph/project-technology/inventory.json")), legacyRuntimeEnabled);
|
|
132
|
+
|
|
115
133
|
const before = fs.readFileSync(path.join(consumer, "graph.json"));
|
|
116
134
|
const preview = technology.execute("enable", consumer, { apply: false });
|
|
117
135
|
check("preview_is_zero_write", preview.status === "preview" && fs.readFileSync(path.join(consumer, "graph.json")).equals(before));
|
|
@@ -147,12 +165,18 @@ try {
|
|
|
147
165
|
apply: true, source: exportPath, targetId: TARGET_ID,
|
|
148
166
|
semanticDigest: SEMANTIC_DIGEST, providerRevision,
|
|
149
167
|
});
|
|
150
|
-
|
|
168
|
+
const consumerContinuity = technology.execute("sync", consumer, {
|
|
169
|
+
apply: true, boundary: "task_complete", continuityEvidence: continuityEvidence("fixture-consumer"),
|
|
170
|
+
});
|
|
171
|
+
check("consumer_binding_ready", connected.status === "success" && consumerContinuity.status === "success" && technology.verify(consumer, { significantWork: true }).status === "success", { connect: connected.blockers, continuity: consumerContinuity.blockers });
|
|
151
172
|
const connectedTwo = technology.execute("connect", consumerTwo, {
|
|
152
173
|
apply: true, source: exportPath, targetId: TARGET_ID,
|
|
153
174
|
semanticDigest: SEMANTIC_DIGEST, providerRevision,
|
|
154
175
|
});
|
|
155
|
-
|
|
176
|
+
const consumerTwoContinuity = technology.execute("sync", consumerTwo, {
|
|
177
|
+
apply: true, boundary: "task_complete", continuityEvidence: continuityEvidence("fixture-consumer-two"),
|
|
178
|
+
});
|
|
179
|
+
check("second_consumer_binding_ready", connectedTwo.status === "success" && consumerTwoContinuity.status === "success" && technology.verify(consumerTwo, { significantWork: true }).status === "success", { connect: connectedTwo.blockers, continuity: consumerTwoContinuity.blockers });
|
|
156
180
|
const repeated = technology.execute("connect", consumer, {
|
|
157
181
|
apply: true, source: exportPath, targetId: TARGET_ID,
|
|
158
182
|
semanticDigest: SEMANTIC_DIGEST, providerRevision,
|