hololake-system-architecture/product-source/hololake-native-desktop/scripts/compile-gls-runtime-registry.mjs

483 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createHash } from 'node:crypto'
import { readFile, readdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
const args = new Map()
for (let index = 2; index < process.argv.length; index += 2) args.set(process.argv[index], process.argv[index + 1])
const explicitSourceRoot = args.get('--source-root')
const repoRoot = args.get('--repo-root') || (explicitSourceRoot ? path.dirname(explicitSourceRoot) : null)
const sourceRoot = explicitSourceRoot || (repoRoot ? path.join(repoRoot, 'gls') : null)
const sourceCommit = args.get('--source-commit')
const output = args.get('--output')
const projectionsPath = args.get('--projections')
const referencesPath = args.get('--references')
if (!repoRoot || !sourceRoot || !sourceCommit || !output || !projectionsPath || !referencesPath) {
throw new Error('usage: compile-gls-runtime-registry.mjs --repo-root <REPO-012> --source-commit <sha> --output <manifest.json> --projections <projections.json> --references <reference-nodes.json>')
}
if (!/^[a-f0-9]{40}$/.test(sourceCommit)) throw new Error('source commit must be a full SHA-1')
async function walk(directory, prefix = '') {
const entries = await readdir(directory, { withFileTypes: true })
const paths = []
for (const entry of entries) {
const relative = prefix ? `${prefix}/${entry.name}` : entry.name
if (entry.isDirectory()) paths.push(...await walk(path.join(directory, entry.name), relative))
else paths.push(relative)
}
return paths
}
async function readRequired(relative) {
return readFile(path.join(repoRoot, relative), 'utf8')
}
function sourceRank(relative) {
if (!relative.includes('/')) return 0
if (relative.startsWith('standards/')) return 1
if (relative.includes('/notion-export/')) return 3
return 2
}
function cleanHeading(line, id) {
return line.replace(/^#+\s*/, '').replaceAll('**', '').replace(new RegExp(`^${id}\\s*[·:-]?\\s*`), '').trim() || id
}
function sourceStatus(raw) {
const line = raw.split(/\r?\n/, 100).find((item) => /^\s*(?:>|[-*]\s*)?(?:状态|Status)\s*[:]/i.test(item))
if (!line) return 'UNSPECIFIED_SOURCE_STATUS'
return line.replace(/^\s*(?:>|[-*]\s*)?(?:状态|Status)\s*[:]\s*/i, '').replaceAll('`', '').trim()
}
function sourceDependencies(raw) {
const match = raw.match(/^\s*depends:\s*\[([^\]]*)\]/m)
return [...new Set(match?.[1].match(/GLS-\d{4}/g) || [])].sort()
}
// REPO-012 的旧 depends 没有声明边语义。Bootstrap Compiler 必须把每条旧边
// 投影为受限、只读的工程类型;这些边永不自动取得运行效力。真正进入激活图的边
// 只能来自 gls-executable-projections/v2 的显式 RUNTIME_REQUIRES。
function typedSourceDependency(sourceId, targetId) {
const number = Number(targetId.slice(4))
const recoveryTargets = new Set(['GLS-0304', 'GLS-0308', 'GLS-0827', 'GLS-0836', 'GLS-0843', 'GLS-0845', 'GLS-0846', 'GLS-0847', 'GLS-0848', 'GLS-0849'])
const bootTargets = new Set(['GLS-0307', 'GLS-0310', 'GLS-0803', 'GLS-0819', 'GLS-0840', 'GLS-0841'])
const buildTargets = new Set(['GLS-0130', 'GLS-0131', 'GLS-0411', 'GLS-0710', 'GLS-0844'])
const evidenceTargets = new Set(['GLS-0306', 'GLS-0311', 'GLS-0604'])
let edgeKind = 'NORMATIVE_REFERENCE'
if (recoveryTargets.has(targetId) && (sourceId >= 'GLS-0800' || sourceId === 'GLS-0304' || sourceId === 'GLS-0308')) edgeKind = 'RECOVERY_REQUIRES'
else if (bootTargets.has(targetId) && sourceId >= 'GLS-0800') edgeKind = 'BOOT_REQUIRES'
else if (buildTargets.has(targetId)) edgeKind = 'BUILD_REQUIRES'
else if (evidenceTargets.has(targetId)) edgeKind = 'EVIDENCE_ONLY'
else if ((number >= 300 && number <= 399) || (number >= 400 && number <= 499) || ['GLS-0602', 'GLS-0603', 'GLS-0605'].includes(targetId)) edgeKind = 'SCHEMA_IMPORT'
return {
edge_kind: edgeKind,
classification_basis: 'BOOTSTRAP_COMPILER_V1_FAMILY_AND_TARGET_RULE',
}
}
function contractKind(family, projectionKind) {
if (projectionKind) return projectionKind
const kinds = {
GLS_ENGINEERING: 'COMPILER_OR_INTERMEDIATE_REPRESENTATION',
GLP: 'MESSAGE_SCHEMA_OR_SYNC_CONTRACT',
GLP_CONTROL_PLANE: 'CONTROL_PLANE_STATE_MACHINE',
GLP_WITNESS: 'APPEND_ONLY_EVIDENCE_LEDGER',
HLDP: 'LANGUAGE_PROGRAM_PROFILE',
GLS_IMPLEMENTATION: 'ADAPTER_MODEL_OR_MODULE_CONTRACT',
AGE: 'EXECUTION_BODY_LIFECYCLE_STATE_MACHINE',
AGE_OS: 'RESOURCE_SCHEDULER',
AGE_AUTONOMOUS_RUNTIME: 'TIME_OR_CAPABILITY_EXTENSION_RUNTIME',
GUANGHU_WORLD: 'WORLD_BOOT_AND_RECOVERY_STATE_MACHINE',
GUANGHU_NATIVE_ENGINEERING: 'NATIVE_QUALITY_GATE',
GUANGHU_NATIVE_OS: 'NATIVE_NODE_RUNTIME_CONTRACT',
GUANGHU_NATIVE_STORAGE: 'NATIVE_STORAGE_CONTRACT',
GUANGHU_PERSONA_GESTATION: 'GESTATIONAL_INGRESS_OR_REVIEW_PIPELINE',
GUANGHU_LANGUAGE_WORLD: 'LANGUAGE_WORLD_BOUNDARY_GUARD',
}
return kinds[family] || 'UNCLASSIFIED_PROTOCOL_SOURCE'
}
function parseTableAuthorities(raw, authorityKind) {
const results = []
for (const line of raw.split(/\r?\n/)) {
const id = line.match(/^\|\s*(GLS-\d{4})\s*\|/)?.[1]
if (!id) continue
results.push({
id,
authority_kind: authorityKind,
declared_source_path: line.match(/`(gls\/[^`]+\.hdlp)`/)?.[1] || null,
})
}
return results
}
function parseSourceManifest(raw) {
const lines = raw.split(/\r?\n/)
const results = []
for (let index = 0; index < lines.length; index += 1) {
const match = lines[index].match(/^\s*-\s+id:\s*["']?(GLS-\d{4})["']?\s*$/)
if (!match) continue
let sourcePath = null
for (let cursor = index + 1; cursor < lines.length && cursor <= index + 12; cursor += 1) {
if (/^\s*-\s+id:/.test(lines[cursor])) break
sourcePath ||= lines[cursor].match(/^\s*source_path:\s*["']?(gls\/[^"']+\.hdlp)["']?\s*$/)?.[1] || null
}
results.push({ id: match[1], authority_kind: 'SOURCE_MANIFEST', declared_source_path: sourcePath })
}
return results
}
function stronglyConnectedComponents(graph) {
let nextIndex = 0
const indices = new Map()
const lowLinks = new Map()
const stack = []
const onStack = new Set()
const components = []
function visit(id) {
indices.set(id, nextIndex)
lowLinks.set(id, nextIndex)
nextIndex += 1
stack.push(id)
onStack.add(id)
for (const target of graph.get(id) || []) {
if (!graph.has(target)) continue
if (!indices.has(target)) {
visit(target)
lowLinks.set(id, Math.min(lowLinks.get(id), lowLinks.get(target)))
} else if (onStack.has(target)) lowLinks.set(id, Math.min(lowLinks.get(id), indices.get(target)))
}
if (lowLinks.get(id) !== indices.get(id)) return
const component = []
let current
do {
current = stack.pop()
onStack.delete(current)
component.push(current)
} while (current !== id)
if (component.length > 1 || (graph.get(id) || []).includes(id)) components.push(component.sort())
}
for (const id of [...graph.keys()].sort()) if (!indices.has(id)) visit(id)
return components.sort((left, right) => left[0].localeCompare(right[0]))
}
const projectionManifest = JSON.parse(await readFile(projectionsPath, 'utf8'))
if (projectionManifest.schema !== 'hololake.gls-executable-projections/v2'
|| projectionManifest.runtime_graph_rule !== 'ONLY_EXPLICIT_RUNTIME_REQUIRES_EDGES_ENTER_ACTIVATION_GRAPH'
|| projectionManifest.source_commit !== sourceCommit) {
throw new Error('projection manifest does not match the selected REPO-012 source commit')
}
const referenceManifest = JSON.parse(await readFile(referencesPath, 'utf8'))
if (referenceManifest.schema !== 'hololake.gls-numbered-reference-nodes/v1'
|| referenceManifest.record_id !== 'HLP-GLS-NUMBERED-REFERENCE-REGISTRY-001'
|| referenceManifest.source?.repository !== 'REPO-012'
|| referenceManifest.source?.commit !== sourceCommit
|| referenceManifest.policy?.number_is_coordinate_not_authority !== true
|| referenceManifest.policy?.independent_protocol_source_required_for_execution !== true
|| referenceManifest.policy?.reference_only_nodes_may_execute !== false
|| referenceManifest.policy?.unknown_reference !== 'FAIL_CLOSED'
|| referenceManifest.policy?.unresolved_number_reference_allowed !== false) {
throw new Error('numbered reference manifest does not match the selected REPO-012 source commit or safety boundary')
}
const protocolRegistryPath = 'gls/GLS-PROTOCOL-REGISTRY.json'
const glsEntryPath = 'gls/GLS-ENTRY.hdlp'
const sourceManifestPath = 'gls/SOURCE-MANIFEST.yml'
const architectureCatalogPath = 'gls/GLS-ARCHITECTURE-CATALOG.hdlp'
const protocolRegistryRaw = await readRequired(protocolRegistryPath)
const glsEntryRaw = await readRequired(glsEntryPath)
const sourceManifestRaw = await readRequired(sourceManifestPath)
const architectureCatalogRaw = await readRequired(architectureCatalogPath)
const protocolRegistry = JSON.parse(protocolRegistryRaw)
const authorityFiles = [
[protocolRegistryPath, protocolRegistryRaw, 'PROTOCOL_REGISTRY'],
[glsEntryPath, glsEntryRaw, 'GLS_ENTRY'],
[sourceManifestPath, sourceManifestRaw, 'SOURCE_MANIFEST'],
[architectureCatalogPath, architectureCatalogRaw, 'ARCHITECTURE_CATALOG'],
].map(([source_path, raw, authority_kind]) => ({
authority_kind,
source_path,
source_sha256: createHash('sha256').update(raw).digest('hex'),
}))
const registryMetadata = new Map()
const authorityDeclarations = new Map()
function addAuthority(declaration) {
const declarations = authorityDeclarations.get(declaration.id) || []
if (!declarations.some((existing) => existing.authority_kind === declaration.authority_kind)) declarations.push(declaration)
authorityDeclarations.set(declaration.id, declarations)
}
for (const entry of protocolRegistry.existing_registered || []) {
registryMetadata.set(entry.id, { ...entry, registry_section: 'existing_registered' })
addAuthority({ id: entry.id, authority_kind: 'PROTOCOL_REGISTRY', declared_source_path: entry.source || null })
}
for (const entry of protocolRegistry.registered_draft_protocols || []) {
registryMetadata.set(entry.id, { ...entry, registry_section: 'registered_draft_protocols' })
addAuthority({ id: entry.id, authority_kind: 'PROTOCOL_REGISTRY', declared_source_path: entry.source || null })
}
for (const declaration of parseTableAuthorities(glsEntryRaw, 'GLS_ENTRY')) addAuthority(declaration)
for (const declaration of parseSourceManifest(sourceManifestRaw)) addAuthority(declaration)
for (const declaration of parseTableAuthorities(architectureCatalogRaw, 'ARCHITECTURE_CATALOG')) addAuthority(declaration)
const routingReferences = new Map()
const routingRoot = path.join(repoRoot, 'routing')
for (const relative of await walk(routingRoot)) {
if (!/\.(?:json|hdlp|md|yml|yaml)$/.test(relative)) continue
const raw = await readFile(path.join(routingRoot, relative), 'utf8')
for (const id of new Set(raw.match(/GLS-\d{4}/g) || [])) {
const refs = routingReferences.get(id) || []
refs.push(`routing/${relative}`)
routingReferences.set(id, refs)
}
}
const allSourceFiles = await walk(sourceRoot)
const candidates = allSourceFiles
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4})(?:[^0-9].*)?\.(hdlp|md)$/) }))
.filter((item) => item.match)
.map((item) => ({ id: item.match[1], relative: item.relative, source_format: item.match[2] === 'hdlp' ? 'HDLP_PROTOCOL_SOURCE' : 'LEGACY_MARKDOWN_EVIDENCE' }))
const grouped = new Map()
for (const candidate of candidates) {
const group = grouped.get(candidate.id) || []
group.push(candidate)
grouped.set(candidate.id, group)
}
const selectedSource = new Map()
for (const [id, sources] of grouped) {
const protocolSources = sources.filter((source) => source.source_format === 'HDLP_PROTOCOL_SOURCE')
if (protocolSources.length === 0) continue
protocolSources.sort((left, right) => sourceRank(left.relative) - sourceRank(right.relative)
|| left.relative.localeCompare(right.relative))
const selected = protocolSources[0]
const relative = selected.relative
selectedSource.set(id, {
relative,
raw: await readFile(path.join(sourceRoot, relative), 'utf8'),
source_format: selected.source_format,
alternate_source_count: protocolSources.length - 1,
})
}
let knownSourceIds = new Set(selectedSource.keys())
const protocolRegistryIds = new Set(registryMetadata.keys())
const draftIds = new Set((protocolRegistry.registered_draft_protocols || []).map((entry) => entry.id))
const legacyDependencyTargets = new Set()
const draftGraph = new Map()
for (const id of draftIds) {
const dependencies = sourceDependencies(selectedSource.get(id)?.raw || '')
draftGraph.set(id, dependencies.filter((target) => draftIds.has(target)))
for (const target of dependencies) legacyDependencyTargets.add(target)
}
const legacyDependencyCycles = stronglyConnectedComponents(draftGraph)
// 旧 Notion 导出只在现行协议真实引用且独立 HDLP 正本缺席时作为历史证据节点进入。
// 未被现行图引用的旧页面不得因为文件名像 GLS 编号就自动取得当前注册地位。
for (const id of [...legacyDependencyTargets].filter((target) => !knownSourceIds.has(target))) {
if (!authorityDeclarations.has(id)) continue
const legacySources = (grouped.get(id) || []).filter((source) => source.source_format === 'LEGACY_MARKDOWN_EVIDENCE')
if (legacySources.length === 0) continue
legacySources.sort((left, right) => sourceRank(left.relative) - sourceRank(right.relative) || left.relative.localeCompare(right.relative))
const selected = legacySources[0]
selectedSource.set(id, {
relative: selected.relative,
raw: await readFile(path.join(sourceRoot, selected.relative), 'utf8'),
source_format: selected.source_format,
alternate_source_count: legacySources.length - 1,
})
}
knownSourceIds = new Set(selectedSource.keys())
const missingSourceTargets = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort()
const referenceIds = new Set()
const referenceNodeNumbers = new Set()
const numberedReferenceNodes = []
for (const node of referenceManifest.nodes || []) {
if (!/^GLS-\d{4}$/.test(node.protocol_id)
|| !/^HLP-GLS-REF-\d{4}$/.test(node.node_number)
|| !node.title
|| !['NORMATIVE_REFERENCE', 'SCHEMA_IMPORT', 'EVIDENCE_ONLY'].includes(node.reference_kind)
|| node.source_state !== 'ROADMAP_REFERENCE_ONLY'
|| !referenceIds.add(node.protocol_id)
|| !referenceNodeNumbers.add(node.node_number)) {
throw new Error(`invalid or duplicated numbered reference node: ${node.protocol_id || 'UNKNOWN'}`)
}
const evidencePaths = []
for (const relative of allSourceFiles) {
if (!/\.(?:hdlp|md|json|ya?ml)$/.test(relative)) continue
const raw = await readFile(path.join(sourceRoot, relative), 'utf8')
if (raw.includes(node.protocol_id)) evidencePaths.push(`gls/${relative}`)
}
if (evidencePaths.length === 0) throw new Error(`numbered reference node has no REPO-012 evidence: ${node.protocol_id}`)
numberedReferenceNodes.push({
...node,
execution_state: 'REFERENCE_ONLY_NOT_EXECUTABLE',
evidence_paths: evidencePaths.sort().slice(0, 12),
})
}
if (missingSourceTargets.length !== referenceIds.size
|| missingSourceTargets.some((id) => !referenceIds.has(id))
|| [...referenceIds].some((id) => !missingSourceTargets.includes(id))) {
throw new Error(`numbered reference registry coverage mismatch: missing=${missingSourceTargets.join(',')} registered=${[...referenceIds].sort().join(',')}`)
}
const protocols = []
for (const [id, source] of [...selectedSource.entries()].sort(([left], [right]) => left.localeCompare(right))) {
const heading = source.raw.split(/\r?\n/).find((line) => line.startsWith('#') && line.includes(id))
const projection = projectionManifest.projections[id]
const metadata = registryMetadata.get(id) || null
const authorities = (authorityDeclarations.get(id) || []).sort((left, right) => left.authority_kind.localeCompare(right.authority_kind))
const declaredPaths = [...new Set(authorities.map((entry) => entry.declared_source_path).filter(Boolean))]
const authorityConflict = declaredPaths.length > 1
const registrationState = authorityConflict
? 'REGISTRATION_CONFLICT'
: authorities.some((entry) => entry.authority_kind === 'PROTOCOL_REGISTRY')
? 'REGISTERED_PROTOCOL_REGISTRY'
: authorities.length > 0
? 'REGISTERED_OTHER_CANONICAL_INDEX'
: 'DISCOVERED_UNRECONCILED'
const legacyDependencies = sourceDependencies(source.raw)
const dependencyEdges = [
...legacyDependencies.map((target) => ({
target,
...typedSourceDependency(id, target),
declared_by: `gls/${source.relative}`,
enters_runtime_graph: false,
target_registered: protocolRegistryIds.has(target),
target_numbered_source_available: knownSourceIds.has(target),
target_number_coordinate_available: knownSourceIds.has(target) || referenceIds.has(target),
target_resolution: knownSourceIds.has(target) ? 'NUMBERED_PROTOCOL_SOURCE' : referenceIds.has(target) ? 'NUMBERED_REFERENCE_NODE' : 'UNRESOLVED',
})),
...(projection?.dependencies || []).map((target) => ({
target,
edge_kind: 'RUNTIME_REQUIRES',
declared_by: 'contracts/gls-executable-projections.json',
enters_runtime_graph: true,
target_registered: protocolRegistryIds.has(target),
target_numbered_source_available: knownSourceIds.has(target),
target_number_coordinate_available: knownSourceIds.has(target) || referenceIds.has(target),
target_resolution: knownSourceIds.has(target) ? 'NUMBERED_PROTOCOL_SOURCE' : referenceIds.has(target) ? 'NUMBERED_REFERENCE_NODE' : 'UNRESOLVED',
})),
]
const activationBlockers = []
if (!projection) activationBlockers.push('NO_EXECUTABLE_ADAPTER')
if (registrationState === 'DISCOVERED_UNRECONCILED') activationBlockers.push('REGISTRATION_NOT_RECONCILED')
if (authorityConflict) activationBlockers.push('REGISTRATION_SOURCE_CONFLICT')
if (legacyDependencies.some((target) => !knownSourceIds.has(target) && !referenceIds.has(target)) && !projection) activationBlockers.push('DEPENDENCY_NUMBER_COORDINATE_MISSING')
protocols.push({
id,
title: cleanHeading(heading || id, id),
source_status: sourceStatus(source.raw),
source_path: `gls/${source.relative}`,
source_format: source.source_format,
source_sha256: createHash('sha256').update(source.raw).digest('hex'),
alternate_source_count: source.alternate_source_count,
registration: {
state: registrationState,
authorities,
declared_source_paths: declaredPaths,
routing_reference_count: (routingReferences.get(id) || []).length,
},
maturity: {
registry_section: metadata?.registry_section || null,
registry_status: metadata?.status || null,
family: metadata?.family || null,
implementation_evidence: metadata?.implementation || null,
},
contract_kind: contractKind(metadata?.family, projection?.projection_kind),
implementation_stage: projection?.stage || null,
projection_state: projection ? 'EXECUTABLE_PROJECTION' : 'INVENTORIED_NOT_EXECUTABLE',
projection_kind: projection?.projection_kind || null,
adapter: projection?.adapter || null,
event_kinds: projection?.event_kinds || [],
dependencies: projection?.dependencies || [],
dependency_edges: dependencyEdges,
activation_blockers: activationBlockers,
})
}
const known = new Set(protocols.map((protocol) => protocol.id))
for (const [id, projection] of Object.entries(projectionManifest.projections)) {
if (!known.has(id)) throw new Error(`projection references an unknown protocol: ${id}`)
for (const dependency of projection.dependencies) {
if (!projectionManifest.projections[dependency]) throw new Error(`${id} depends on a protocol without an executable projection: ${dependency}`)
}
}
const visiting = new Set()
const visited = new Set()
function visit(id) {
if (visiting.has(id)) throw new Error(`executable protocol dependency cycle at ${id}`)
if (visited.has(id)) return
visiting.add(id)
for (const dependency of projectionManifest.projections[id].dependencies) visit(dependency)
visiting.delete(id)
visited.add(id)
}
for (const id of Object.keys(projectionManifest.projections)) visit(id)
const executableCount = protocols.filter((protocol) => protocol.projection_state === 'EXECUTABLE_PROJECTION').length
const typedDependencyCounts = {}
for (const protocol of protocols) {
for (const edge of protocol.dependency_edges.filter((candidate) => !candidate.enters_runtime_graph)) {
typedDependencyCounts[edge.edge_kind] = (typedDependencyCounts[edge.edge_kind] || 0) + 1
}
}
const dependenciesNotInRegistry = [...legacyDependencyTargets].filter((id) => !protocolRegistryIds.has(id)).sort()
const dependenciesWithoutNumberedSource = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort()
const unresolvedNumberReferences = dependenciesWithoutNumberedSource.filter((id) => !referenceIds.has(id))
const numberedSourcesNotInProtocolRegistry = [...knownSourceIds].filter((id) => !protocolRegistryIds.has(id)).sort()
const registry = {
schema: 'hololake.gls-runtime-manifest/v2',
record_id: 'HLP-GLS-RUNTIME-MANIFEST-002',
source: {
repository: 'REPO-012',
commit: sourceCommit,
root: 'gls',
authority_files: authorityFiles,
},
compiler: {
source_protocol_is_human_and_machine_authority: true,
raw_protocol_text_executed: false,
arbitrary_protocol_code_allowed: false,
executable_projection_requires_explicit_adapter: true,
unprojected_protocol_behavior: 'INVENTORIED_NOT_EXECUTABLE',
source_dependency_behavior: 'TYPED_AUDIT_ONLY_NEVER_ACTIVATES',
runtime_graph_source: 'EXPLICIT_EXECUTABLE_PROJECTIONS_ONLY',
runtime_dependency_cycles: 'REJECT',
unknown_protocol: 'FAIL_CLOSED',
},
reconciliation: {
numbered_protocol_count: protocols.length,
protocol_registry_id_count: protocolRegistryIds.size,
existing_registered_count: (protocolRegistry.existing_registered || []).length,
registered_draft_count: (protocolRegistry.registered_draft_protocols || []).length,
registered_draft_not_started_count: (protocolRegistry.registered_draft_protocols || []).filter((entry) => entry.implementation === 'NOT_STARTED').length,
legacy_dependency_target_count: legacyDependencyTargets.size,
dependencies_not_in_protocol_registry: dependenciesNotInRegistry,
dependencies_without_numbered_source: dependenciesWithoutNumberedSource,
numbered_reference_node_count: numberedReferenceNodes.length,
unresolved_number_references: unresolvedNumberReferences,
unresolved_number_reference_count: unresolvedNumberReferences.length,
every_dependency_has_number_coordinate: unresolvedNumberReferences.length === 0,
numbered_sources_not_in_protocol_registry: numberedSourcesNotInProtocolRegistry,
legacy_dependency_cycles: legacyDependencyCycles,
source_reference_cycles: legacyDependencyCycles,
typed_source_dependency_counts: Object.fromEntries(Object.entries(typedDependencyCounts).sort()),
unclassified_source_dependency_count: 0,
discovered_unreconciled_count: protocols.filter((protocol) => protocol.registration.state === 'DISCOVERED_UNRECONCILED').length,
authority_conflict_count: protocols.filter((protocol) => protocol.registration.state === 'REGISTRATION_CONFLICT').length,
},
protocol_count: protocols.length,
executable_projection_count: executableCount,
inventoried_not_executable_count: protocols.length - executableCount,
number_coordinate_count: protocols.length + numberedReferenceNodes.length,
numbered_reference_nodes: numberedReferenceNodes,
protocols,
}
await writeFile(output, `${JSON.stringify(registry, null, 2)}\n`)
console.log(`GLS_RUNTIME_MANIFEST_COMPILED protocols=${protocols.length} references=${numberedReferenceNodes.length} unresolved=${unresolvedNumberReferences.length} registered=${protocolRegistryIds.size} executable=${executableCount} legacy_cycles=${legacyDependencyCycles.length} output=${output}`)