toCanonicalMap method
A stable, JSON-encodable canonical form of the whole spec: keys sorted
at every level, lists sorted (semantically orderless), durations as
seconds, null fields omitted. Two specs that parse to the same
policy always produce an equal map — this is the content-addressed
cache key input.
Implementation
Map<String, Object?> toCanonicalMap() {
return {
'apiVersion': 'fa/v1',
'kind': 'Cube',
'metadata': {
if (description != null) 'description': description,
'name': name,
},
'spec': {
'backend': backend.name,
// Omitted when false so existing specs keep byte-identical cache
// keys.
if (allowDegrade) 'allowDegrade': true,
'cache': {
'enabled': cache.enabled,
if (cache.paths.isNotEmpty) 'paths': [...cache.paths]..sort(),
'restore': cache.restore,
if (cache.ttl != null) 'ttl': cache.ttl!.inSeconds,
},
'env': [
for (final variable in [...env.vars]..sort(_byName))
switch (variable) {
CubeEnvValue(:final name, :final value) => {
'name': name,
'value': value,
},
CubeEnvValueFrom(:final name, :final source) => {
'name': name,
'valueFrom': source,
},
CubeEnvHidden(:final name) => {'hidden': true, 'name': name},
},
],
'filesystem': {
'mounts': [
for (final mount in [...filesystem.mounts]..sort(_byPath))
{'access': mount.access.label, 'path': mount.path},
],
'workspace': filesystem.workspace,
},
'network': {
'allow': [
for (final rule in [...network.allow]..sort(_byHost))
_canonicalRule(rule),
],
'deny': [
for (final rule in [...network.deny]..sort(_byHost))
_canonicalRule(rule),
],
},
'resources': {
if (resources.cpu != null) 'cpu': resources.cpu,
if (resources.diskBytes != null) 'diskBytes': resources.diskBytes,
if (resources.memoryBytes != null)
'memoryBytes': resources.memoryBytes,
if (resources.timeout != null)
'timeout': resources.timeout!.inSeconds,
},
'tools': {
'allow': [...tools.allow]..sort(),
'deny': [...tools.deny]..sort(),
},
},
};
}