@serve.zone/interfaces 32.50.0 → 32.51.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/changelog.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-10-06 - 32.51.0
4
+
5
+ ### Features
6
+
7
+ - A legacy service's runtime attestation is captured from an administrator's host observation, so
8
+ it no longer needs a fleet Coreflow session or Spark, which legacy hosts do not run.
9
+ `IRequest_Any_Cloudly_CaptureLegacyServiceRuntimeAttestation` now carries `hostObservation`
10
+ (`ILegacyServiceHostObservation`, `data/deploymentlegacyimport.ts`): the Swarm service name, spec
11
+ image and mode (`TLegacyHostSwarmServiceMode`, replicated with its desired replicas, or global),
12
+ and per replica the node hostname, Swarm task id, full container id, the engine's image store
13
+ (`TLegacyHostImageStore`), the container's image id, the image's `RepoDigests`, the container
14
+ state (`TLegacyHostContainerState`) and its health (`TLegacyHostContainerHealth`, `none` when no
15
+ healthcheck applies). `validateLegacyServiceHostObservation` judges its exact shape,
16
+ `selectLegacyServiceHostObservationRootDigest` names the one root every replica runs from the
17
+ service's own repository, and `judgeLegacyServiceHostObservation` judges it against that root as
18
+ Cloudly reads it: exactly the desired replicas of a replicated service, a spec image naming the
19
+ repository by the source tag or the root digest, every replica running and healthy or without a
20
+ healthcheck (`unhealthy` and `starting` are refused), and each container's image id the root
21
+ digest on the containerd image store or a platform image configuration digest of the root on the
22
+ classic store. The source tag need not still name the root.
23
+ `ILegacyServiceRuntimeAttestation.data.runtime` records `evidenceSource`
24
+ (`TLegacyServiceRuntimeEvidenceSource`, `administrator-host-observation`) and the
25
+ `hostObservation` it was built from; `validateLegacyServiceRuntimeAttestation` requires both and
26
+ that `requiredReplicaCount` and `tasks` are the observed desired replicas and Swarm tasks.
27
+ The capture request's `hostObservation` and the record's `evidenceSource`/`hostObservation` are
28
+ new required members, which stays a minor of the one common major 32 because the previous capture
29
+ had no success path on a fleet without a Coreflow session, no shipped store holds an attestation
30
+ record, and Cloudly is the only code consumer.
31
+
32
+ ### Maintenance
33
+
34
+ - Release tooling: `@git.zone/tstest` 7.1.0 → 7.2.0. pnpm stays at 12.8.1 until 12.8.2 is seven days old (2026-10-07 01:15 UTC); `@types/node` stays at 26.6.3 until 26.6.4 is seven days old (2026-10-08 22:39 UTC).
35
+
3
36
  ## 2026-10-06 - 32.50.0
4
37
 
5
38
  ### Features
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.50.0',
6
+ version: '32.51.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
@@ -4,8 +4,10 @@ import type { TImmutableContainerPlatform, TOciManifestMediaType, TSha256Digest
4
4
  * service already runs, instead of one built from a source revision.
5
5
  *
6
6
  * A legacy service without deployable source still has an image in Cloudly's registry and, until
7
- * its runtime is frozen, healthy replicas running it. An administrator captures that fact once as
8
- * a {@link ILegacyServiceRuntimeAttestation} while the replicas still run. A greenfield successor
7
+ * its runtime is frozen, replicas running it. An administrator observes those replicas on the
8
+ * hosts that run them ({@link ILegacyServiceHostObservation}) while they still run, and Cloudly
9
+ * captures that observation once as a {@link ILegacyServiceRuntimeAttestation} after it verified
10
+ * the observed root against its own registry. A greenfield successor
9
11
  * reservation names the attestation in `legacyImport` instead of a `sourceRevision`, and Cloudly
10
12
  * then imports the attested image into the successor's own repository and release tag itself:
11
13
  * the bytes never leave the registry and the release records that it was imported, not built.
@@ -44,18 +46,86 @@ export interface ILegacyServiceImageSource {
44
46
  */
45
47
  platforms: ILegacyImagePlatformManifest[];
46
48
  }
47
- /** One healthy replica of the legacy service, observed running the attested digest. */
49
+ /**
50
+ * One replica of the legacy service, observed running the attested digest in its own stated
51
+ * health: healthy, or running with no healthcheck that could state one.
52
+ */
48
53
  export interface ILegacyServiceRuntimeTaskAttestation {
54
+ /** The replica's runtime identity: the Swarm task it was observed as. */
49
55
  deploymentId: string;
56
+ /** The hostname of the node the replica was observed on. */
50
57
  nodeName: string;
51
58
  observedDigest: TSha256Digest;
52
59
  }
60
+ /**
61
+ * Where the runtime part of an attestation comes from. `administrator-host-observation`: an
62
+ * administrator read the legacy Swarm service and its containers on the hosts that run them, and
63
+ * Cloudly judged that observation against its own registry.
64
+ */
65
+ export type TLegacyServiceRuntimeEvidenceSource = 'administrator-host-observation';
66
+ /** A container's state as the Docker engine reports it (`State.Status`). */
67
+ export type TLegacyHostContainerState = 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead';
68
+ /**
69
+ * A container's health as the Docker engine reports it (`State.Health.Status`). `none`: no
70
+ * healthcheck applies to the container — the image declares none and the service adds none — so
71
+ * the engine reports no health state at all.
72
+ */
73
+ export type TLegacyHostContainerHealth = 'healthy' | 'unhealthy' | 'starting' | 'none';
74
+ /**
75
+ * The image store of the Docker engine that runs a container. On the `containerd` store a
76
+ * container's image id is the digest of the root it was pulled as; on the `classic` store it is
77
+ * the image configuration digest of the platform manifest the engine pulled.
78
+ */
79
+ export type TLegacyHostImageStore = 'containerd' | 'classic';
80
+ /** A Swarm service's mode (`Spec.Mode`): a replicated service states its desired replicas. */
81
+ export type TLegacyHostSwarmServiceMode = {
82
+ kind: 'replicated';
83
+ desiredReplicas: number;
84
+ } | {
85
+ kind: 'global';
86
+ };
87
+ /** One replica of a legacy Swarm service, as an administrator read it on the node that runs it. */
88
+ export interface ILegacyHostReplicaObservation {
89
+ /** The hostname of the node the task runs on. */
90
+ nodeHostname: string;
91
+ /** The Swarm task id (25 lowercase base-36 characters). */
92
+ swarmTaskId: string;
93
+ /** The full container id of the task (64 lowercase hex characters). */
94
+ containerId: string;
95
+ imageStore: TLegacyHostImageStore;
96
+ /** The container's image id (`Image`), a sha256 digest. */
97
+ containerImageId: TSha256Digest;
98
+ /** The `RepoDigests` of the container's image, exactly as the engine lists them. */
99
+ imageRepoDigests: string[];
100
+ state: TLegacyHostContainerState;
101
+ health: TLegacyHostContainerHealth;
102
+ }
103
+ /**
104
+ * An administrator's observation of the legacy Swarm service of one legacy service and of every
105
+ * container its tasks run, read on the hosts while the legacy runtime still runs.
106
+ *
107
+ * It states facts, not conclusions: Cloudly judges it against its own registry
108
+ * ({@link judgeLegacyServiceHostObservation}) before it attests anything.
109
+ */
110
+ export interface ILegacyServiceHostObservation {
111
+ /** The Swarm service name. */
112
+ swarmServiceName: string;
113
+ /** The image reference of the Swarm service spec (`Spec.TaskTemplate.ContainerSpec.Image`). */
114
+ specImage: string;
115
+ mode: TLegacyHostSwarmServiceMode;
116
+ /** When the administrator read the hosts, in milliseconds since the epoch. */
117
+ observedAt: number;
118
+ /** Every running task's replica, sorted by `swarmTaskId`. */
119
+ replicas: ILegacyHostReplicaObservation[];
120
+ }
53
121
  /**
54
122
  * Durable evidence, captured by an administrator while the legacy runtime still ran, that every
55
- * required replica of a legacy service was healthy and ran exactly the registry root it names.
123
+ * desired replica of a legacy service ran, in its own stated health, exactly the registry root it
124
+ * names.
56
125
  *
57
126
  * Cloudly writes one and never changes it. A capture only succeeds on complete evidence, so a
58
- * stored attestation is by construction one whose runtime digest is its source root digest.
127
+ * stored attestation is by construction one whose runtime digest is its source root digest and
128
+ * whose runtime part is exactly the host observation it records.
59
129
  */
60
130
  export interface ILegacyServiceRuntimeAttestation {
61
131
  id: string;
@@ -64,11 +134,16 @@ export interface ILegacyServiceRuntimeAttestation {
64
134
  organizationId: string;
65
135
  source: ILegacyServiceImageSource;
66
136
  runtime: {
137
+ /** The desired replicas of the observed replicated Swarm service. */
67
138
  requiredReplicaCount: number;
139
+ /** Replicas observed running healthy, or running with no healthcheck (`health: 'none'`). */
68
140
  healthyReplicaCount: number;
69
141
  runtimeDigest: TSha256Digest;
70
- /** Sorted by deploymentId, one per required replica. */
142
+ /** Sorted by deploymentId, one per required replica: the observed replicas. */
71
143
  tasks: ILegacyServiceRuntimeTaskAttestation[];
144
+ evidenceSource: TLegacyServiceRuntimeEvidenceSource;
145
+ /** The observation the runtime part was built from, as the administrator submitted it. */
146
+ hostObservation: ILegacyServiceHostObservation;
72
147
  };
73
148
  capturedByUserId: string;
74
149
  capturedAt: number;
@@ -89,4 +164,43 @@ export declare const isLegacyImagePlatformManifestMediaType: (valueArg: unknown)
89
164
  /** True when a media type names an index of either family. */
90
165
  export declare const isLegacyImageImportIndexMediaType: (valueArg: unknown) => valueArg is TLegacyImageImportIndexMediaType;
91
166
  export declare const validateLegacyImageImportReference: (referenceArg: unknown) => string[];
167
+ /**
168
+ * The shape of a host observation: exact keys at every level, Docker's own value sets, at most 64
169
+ * replicas, unique and sorted by Swarm task id, each container observed once. It judges nothing
170
+ * against a registry; {@link judgeLegacyServiceHostObservation} does.
171
+ */
172
+ export declare const validateLegacyServiceHostObservation: (observationArg: unknown, labelArg?: string) => string[];
173
+ /** True when a value is a well-formed host observation. */
174
+ export declare const isLegacyServiceHostObservation: (valueArg: unknown) => valueArg is ILegacyServiceHostObservation;
175
+ /**
176
+ * The one root digest every observed replica runs from the legacy service's own repository, which
177
+ * is the digest Cloudly then inspects in its registry. Refused when a replica's root from that
178
+ * repository is unknown, when the replicas run more than one root, or when nothing was observed.
179
+ */
180
+ export declare const selectLegacyServiceHostObservationRootDigest: (argsArg: {
181
+ observation: ILegacyServiceHostObservation;
182
+ registryHost: string;
183
+ repository: string;
184
+ }) => {
185
+ rootDigest?: TSha256Digest;
186
+ errors: string[];
187
+ };
188
+ /**
189
+ * Judges a well-formed host observation against the registry source Cloudly inspected for it.
190
+ * Every rule an attestation's runtime part must meet:
191
+ *
192
+ * - the Swarm service is replicated, with at least one desired replica, and exactly its desired
193
+ * replicas were observed;
194
+ * - its spec image names the source repository by the source tag, by the root digest, or by both;
195
+ * - every replica is `running`, and `healthy` or without a healthcheck (`none`) — `unhealthy` and
196
+ * `starting` are refused;
197
+ * - every replica runs the source root from the source repository: its image lists
198
+ * `registryHost/repository@rootDigest`, and its image id is the root digest on the containerd
199
+ * store or one of the root's platform image configuration digests on the classic store.
200
+ */
201
+ export declare const judgeLegacyServiceHostObservation: (argsArg: {
202
+ observation: ILegacyServiceHostObservation;
203
+ source: ILegacyServiceImageSource;
204
+ label?: string;
205
+ }) => string[];
92
206
  export declare const validateLegacyServiceRuntimeAttestation: (attestationArg: unknown) => string[];
@@ -1,5 +1,5 @@
1
1
  import { normalizeSha256Digest } from './immutableimage.js';
2
- import { hasExactKeys, isPlainRecord } from '../private/dataproperties.js';
2
+ import { hasExactKeys, isPlainRecord, readDataElements, readDataProperties, } from '../private/dataproperties.js';
3
3
  const identifierRegex = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/;
4
4
  /** An OCI tag, the mutable `latest` included: the source tag is whatever the legacy service ran. */
5
5
  const ociTagRegex = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
@@ -16,6 +16,31 @@ const legacyImageImportIndexMediaTypes = [
16
16
  'application/vnd.docker.distribution.manifest.list.v2+json',
17
17
  ];
18
18
  const legacyImagePlatforms = ['linux/amd64', 'linux/arm64'];
19
+ const legacyHostContainerStates = [
20
+ 'created',
21
+ 'running',
22
+ 'paused',
23
+ 'restarting',
24
+ 'removing',
25
+ 'exited',
26
+ 'dead',
27
+ ];
28
+ const legacyHostContainerHealths = [
29
+ 'healthy',
30
+ 'unhealthy',
31
+ 'starting',
32
+ 'none',
33
+ ];
34
+ /** The health states a replica may be attested in: healthy, or no healthcheck at all. */
35
+ const attestableLegacyHostContainerHealths = ['healthy', 'none'];
36
+ const legacyHostImageStores = ['containerd', 'classic'];
37
+ const swarmTaskIdRegex = /^[a-z0-9]{25}$/;
38
+ const containerIdRegex = /^[0-9a-f]{64}$/;
39
+ const swarmServiceNameRegex = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
40
+ const nodeHostnameRegex = /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
41
+ const imageReferenceRegex = /^\S{1,512}$/;
42
+ const repoDigestRegex = /^[^\s@]{1,440}@sha256:[0-9a-f]{64}$/;
43
+ const maximumRepoDigestCount = 32;
19
44
  const isIdentifier = (valueArg) => (typeof valueArg === 'string' && identifierRegex.test(valueArg));
20
45
  const isCanonicalDigest = (valueArg) => (typeof valueArg === 'string' && normalizeSha256Digest(valueArg) === valueArg);
21
46
  const isPositiveSafeInteger = (valueArg, maximumArg) => (typeof valueArg === 'number'
@@ -44,6 +69,232 @@ export const validateLegacyImageImportReference = (referenceArg) => {
44
69
  }
45
70
  return errors;
46
71
  };
72
+ const validateHostReplicaObservation = (replicaArg, labelArg) => {
73
+ const replica = readDataProperties(replicaArg);
74
+ if (replica === null)
75
+ return [`${labelArg} must be a plain data object`];
76
+ const errors = [];
77
+ if (!hasExactKeys(replica, [
78
+ 'nodeHostname',
79
+ 'swarmTaskId',
80
+ 'containerId',
81
+ 'imageStore',
82
+ 'containerImageId',
83
+ 'imageRepoDigests',
84
+ 'state',
85
+ 'health',
86
+ ])) {
87
+ errors.push(`${labelArg} must use its exact schema`);
88
+ }
89
+ if (typeof replica.nodeHostname !== 'string' || !nodeHostnameRegex.test(replica.nodeHostname)) {
90
+ errors.push(`${labelArg}.nodeHostname must be a host name`);
91
+ }
92
+ if (typeof replica.swarmTaskId !== 'string' || !swarmTaskIdRegex.test(replica.swarmTaskId)) {
93
+ errors.push(`${labelArg}.swarmTaskId must be a Swarm task id`);
94
+ }
95
+ if (typeof replica.containerId !== 'string' || !containerIdRegex.test(replica.containerId)) {
96
+ errors.push(`${labelArg}.containerId must be a full container id`);
97
+ }
98
+ if (!legacyHostImageStores.some((storeArg) => storeArg === replica.imageStore)) {
99
+ errors.push(`${labelArg}.imageStore must be containerd or classic`);
100
+ }
101
+ if (!isCanonicalDigest(replica.containerImageId)) {
102
+ errors.push(`${labelArg}.containerImageId must be a canonical sha256 digest`);
103
+ }
104
+ const repoDigests = readDataElements(replica.imageRepoDigests, maximumRepoDigestCount);
105
+ if (repoDigests === null
106
+ || repoDigests.some((entryArg) => typeof entryArg !== 'string' || !repoDigestRegex.test(entryArg))
107
+ || new Set(repoDigests).size !== repoDigests.length) {
108
+ errors.push(`${labelArg}.imageRepoDigests must be unique digested image references`);
109
+ }
110
+ if (!legacyHostContainerStates.some((stateArg) => stateArg === replica.state)) {
111
+ errors.push(`${labelArg}.state must be a Docker container state`);
112
+ }
113
+ if (!legacyHostContainerHealths.some((healthArg) => healthArg === replica.health)) {
114
+ errors.push(`${labelArg}.health must be healthy, unhealthy, starting or none`);
115
+ }
116
+ return errors;
117
+ };
118
+ const validateHostSwarmServiceMode = (modeArg, labelArg) => {
119
+ const mode = readDataProperties(modeArg);
120
+ if (mode === null)
121
+ return [`${labelArg} must be a plain data object`];
122
+ if (mode.kind === 'global') {
123
+ return hasExactKeys(mode, ['kind']) ? [] : [`${labelArg} must use its exact schema`];
124
+ }
125
+ if (mode.kind !== 'replicated')
126
+ return [`${labelArg}.kind must be replicated or global`];
127
+ const errors = [];
128
+ if (!hasExactKeys(mode, ['kind', 'desiredReplicas'])) {
129
+ errors.push(`${labelArg} must use its exact schema`);
130
+ }
131
+ if (typeof mode.desiredReplicas !== 'number'
132
+ || !Number.isSafeInteger(mode.desiredReplicas)
133
+ || mode.desiredReplicas < 0
134
+ || mode.desiredReplicas > maximumReplicaCount) {
135
+ errors.push(`${labelArg}.desiredReplicas must be a bounded replica count`);
136
+ }
137
+ return errors;
138
+ };
139
+ /**
140
+ * The shape of a host observation: exact keys at every level, Docker's own value sets, at most 64
141
+ * replicas, unique and sorted by Swarm task id, each container observed once. It judges nothing
142
+ * against a registry; {@link judgeLegacyServiceHostObservation} does.
143
+ */
144
+ export const validateLegacyServiceHostObservation = (observationArg, labelArg = 'hostObservation') => {
145
+ const observation = readDataProperties(observationArg);
146
+ if (observation === null)
147
+ return [`${labelArg} must be a plain data object`];
148
+ const errors = [];
149
+ if (!hasExactKeys(observation, ['swarmServiceName', 'specImage', 'mode', 'observedAt', 'replicas'])) {
150
+ errors.push(`${labelArg} must use its exact schema`);
151
+ }
152
+ if (typeof observation.swarmServiceName !== 'string'
153
+ || !swarmServiceNameRegex.test(observation.swarmServiceName)) {
154
+ errors.push(`${labelArg}.swarmServiceName must be a Swarm service name`);
155
+ }
156
+ if (typeof observation.specImage !== 'string' || !imageReferenceRegex.test(observation.specImage)) {
157
+ errors.push(`${labelArg}.specImage must be a bounded image reference`);
158
+ }
159
+ errors.push(...validateHostSwarmServiceMode(observation.mode, `${labelArg}.mode`));
160
+ if (!isPositiveSafeInteger(observation.observedAt, Number.MAX_SAFE_INTEGER)) {
161
+ errors.push(`${labelArg}.observedAt must be a positive timestamp`);
162
+ }
163
+ const replicas = readDataElements(observation.replicas, maximumReplicaCount);
164
+ if (replicas === null) {
165
+ errors.push(`${labelArg}.replicas must be a list of at most ${maximumReplicaCount} replicas`);
166
+ return errors;
167
+ }
168
+ replicas.forEach((replicaArg, indexArg) => {
169
+ errors.push(...validateHostReplicaObservation(replicaArg, `${labelArg}.replicas[${indexArg}]`));
170
+ });
171
+ const readReplicaKey = (replicaArg, keyArg) => (readDataProperties(replicaArg)?.[keyArg]);
172
+ const taskIds = replicas.map((replicaArg) => readReplicaKey(replicaArg, 'swarmTaskId'));
173
+ const sortedTaskIds = taskIds
174
+ .filter((idArg) => typeof idArg === 'string')
175
+ .sort();
176
+ if (new Set(taskIds).size !== taskIds.length
177
+ || JSON.stringify(taskIds) !== JSON.stringify(sortedTaskIds)) {
178
+ errors.push(`${labelArg}.replicas must be unique and sorted by swarmTaskId`);
179
+ }
180
+ const containerIds = replicas.map((replicaArg) => readReplicaKey(replicaArg, 'containerId'));
181
+ if (new Set(containerIds).size !== containerIds.length) {
182
+ errors.push(`${labelArg}.replicas must observe each container once`);
183
+ }
184
+ return errors;
185
+ };
186
+ /** True when a value is a well-formed host observation. */
187
+ export const isLegacyServiceHostObservation = (valueArg) => (validateLegacyServiceHostObservation(valueArg).length === 0);
188
+ /**
189
+ * The root digest one replica was pulled as from `registryHost/repository`.
190
+ *
191
+ * On the containerd store that is the container's image id, which the image's `RepoDigests` must
192
+ * list for the repository. On the classic store it is the one digest `RepoDigests` lists for the
193
+ * repository; none, or more than one, leaves the root unknown.
194
+ */
195
+ const selectReplicaRootDigest = (replicaArg, repositoryReferenceArg) => {
196
+ const prefix = `${repositoryReferenceArg}@`;
197
+ const repositoryDigests = replicaArg.imageRepoDigests
198
+ .filter((entryArg) => entryArg.startsWith(prefix))
199
+ .map((entryArg) => normalizeSha256Digest(entryArg.slice(prefix.length)))
200
+ .filter((digestArg) => digestArg !== undefined);
201
+ if (replicaArg.imageStore === 'containerd') {
202
+ return repositoryDigests.includes(replicaArg.containerImageId)
203
+ ? replicaArg.containerImageId
204
+ : undefined;
205
+ }
206
+ return repositoryDigests.length === 1 ? repositoryDigests[0] : undefined;
207
+ };
208
+ /**
209
+ * The one root digest every observed replica runs from the legacy service's own repository, which
210
+ * is the digest Cloudly then inspects in its registry. Refused when a replica's root from that
211
+ * repository is unknown, when the replicas run more than one root, or when nothing was observed.
212
+ */
213
+ export const selectLegacyServiceHostObservationRootDigest = (argsArg) => {
214
+ const repositoryReference = `${argsArg.registryHost}/${argsArg.repository}`;
215
+ const errors = [];
216
+ const roots = new Set();
217
+ argsArg.observation.replicas.forEach((replicaArg, indexArg) => {
218
+ const root = selectReplicaRootDigest(replicaArg, repositoryReference);
219
+ if (root === undefined) {
220
+ errors.push(replicaArg.imageStore === 'containerd'
221
+ ? `hostObservation.replicas[${indexArg}] image is not listed as ${repositoryReference}@<its image id>`
222
+ : `hostObservation.replicas[${indexArg}] image lists not exactly one digest of ${repositoryReference}`);
223
+ return;
224
+ }
225
+ roots.add(root);
226
+ });
227
+ if (argsArg.observation.replicas.length === 0) {
228
+ errors.push('hostObservation must observe at least one replica');
229
+ }
230
+ if (roots.size > 1) {
231
+ errors.push('hostObservation replicas run more than one root digest');
232
+ }
233
+ const [rootDigest] = [...roots];
234
+ return errors.length === 0 && rootDigest !== undefined ? { rootDigest, errors } : { errors };
235
+ };
236
+ /**
237
+ * Judges a well-formed host observation against the registry source Cloudly inspected for it.
238
+ * Every rule an attestation's runtime part must meet:
239
+ *
240
+ * - the Swarm service is replicated, with at least one desired replica, and exactly its desired
241
+ * replicas were observed;
242
+ * - its spec image names the source repository by the source tag, by the root digest, or by both;
243
+ * - every replica is `running`, and `healthy` or without a healthcheck (`none`) — `unhealthy` and
244
+ * `starting` are refused;
245
+ * - every replica runs the source root from the source repository: its image lists
246
+ * `registryHost/repository@rootDigest`, and its image id is the root digest on the containerd
247
+ * store or one of the root's platform image configuration digests on the classic store.
248
+ */
249
+ export const judgeLegacyServiceHostObservation = (argsArg) => {
250
+ const label = argsArg.label ?? 'hostObservation';
251
+ const { observation, source } = argsArg;
252
+ const errors = [];
253
+ if (observation.mode.kind !== 'replicated') {
254
+ errors.push(`${label}.mode must be replicated: a legacy service runs as a replicated Swarm service`);
255
+ }
256
+ else if (observation.mode.desiredReplicas < 1) {
257
+ errors.push(`${label}.mode must desire at least one replica`);
258
+ }
259
+ else if (observation.replicas.length !== observation.mode.desiredReplicas) {
260
+ errors.push(`${label} must observe exactly the ${observation.mode.desiredReplicas} desired replicas`);
261
+ }
262
+ const repositoryReference = `${source.registryHost}/${source.repository}`;
263
+ const specImages = [
264
+ `${repositoryReference}:${source.tag}`,
265
+ `${repositoryReference}@${source.rootDigest}`,
266
+ `${repositoryReference}:${source.tag}@${source.rootDigest}`,
267
+ ];
268
+ if (!specImages.includes(observation.specImage)) {
269
+ errors.push(`${label}.specImage must name ${repositoryReference} by its source tag or root digest`);
270
+ }
271
+ const imageConfigDigests = source.platforms.map((platformArg) => platformArg.imageConfigDigest);
272
+ observation.replicas.forEach((replicaArg, indexArg) => {
273
+ const replicaLabel = `${label}.replicas[${indexArg}]`;
274
+ if (replicaArg.state !== 'running') {
275
+ errors.push(`${replicaLabel}.state must be running`);
276
+ }
277
+ if (!attestableLegacyHostContainerHealths.includes(replicaArg.health)) {
278
+ errors.push(`${replicaLabel}.health must be healthy or none`);
279
+ }
280
+ if (!replicaArg.imageRepoDigests.includes(`${repositoryReference}@${source.rootDigest}`)) {
281
+ errors.push(`${replicaLabel} image must be ${repositoryReference}@${source.rootDigest}`);
282
+ }
283
+ if (replicaArg.imageStore === 'containerd') {
284
+ if (replicaArg.containerImageId !== source.rootDigest) {
285
+ errors.push(`${replicaLabel}.containerImageId must be the root digest on the containerd image store`);
286
+ }
287
+ return;
288
+ }
289
+ if (!imageConfigDigests.includes(replicaArg.containerImageId)) {
290
+ errors.push(`${replicaLabel}.containerImageId must be a platform image configuration digest of the root on the classic image store`);
291
+ }
292
+ if (selectReplicaRootDigest(replicaArg, repositoryReference) === undefined) {
293
+ errors.push(`${replicaLabel} image must list exactly one digest of ${repositoryReference} on the classic image store`);
294
+ }
295
+ });
296
+ return errors;
297
+ };
47
298
  const validatePlatformManifest = (manifestArg, indexArg) => {
48
299
  const label = `source.platforms[${indexArg}]`;
49
300
  if (!isPlainRecord(manifestArg))
@@ -137,18 +388,51 @@ const validateImageSource = (sourceArg) => {
137
388
  }
138
389
  return errors;
139
390
  };
140
- const validateRuntime = (runtimeArg, rootDigestArg) => {
391
+ const isLegacyServiceImageSource = (valueArg) => (validateImageSource(valueArg).length === 0);
392
+ const validateRuntime = (runtimeArg, sourceArg) => {
141
393
  if (!isPlainRecord(runtimeArg))
142
394
  return ['runtime must be an object'];
395
+ const rootDigest = isPlainRecord(sourceArg) ? sourceArg.rootDigest : undefined;
143
396
  const errors = [];
144
397
  if (!hasExactKeys(runtimeArg, [
145
398
  'requiredReplicaCount',
146
399
  'healthyReplicaCount',
147
400
  'runtimeDigest',
148
401
  'tasks',
402
+ 'evidenceSource',
403
+ 'hostObservation',
149
404
  ])) {
150
405
  errors.push('runtime must use its exact schema');
151
406
  }
407
+ if (runtimeArg.evidenceSource !== 'administrator-host-observation') {
408
+ errors.push('runtime.evidenceSource must be administrator-host-observation');
409
+ }
410
+ const hostObservation = runtimeArg.hostObservation;
411
+ const observationErrors = validateLegacyServiceHostObservation(hostObservation, 'runtime.hostObservation');
412
+ errors.push(...observationErrors);
413
+ if (observationErrors.length === 0
414
+ && isLegacyServiceHostObservation(hostObservation)
415
+ && isLegacyServiceImageSource(sourceArg)) {
416
+ errors.push(...judgeLegacyServiceHostObservation({
417
+ observation: hostObservation,
418
+ source: sourceArg,
419
+ label: 'runtime.hostObservation',
420
+ }));
421
+ if (hostObservation.mode.kind === 'replicated'
422
+ && runtimeArg.requiredReplicaCount !== hostObservation.mode.desiredReplicas) {
423
+ errors.push('runtime.requiredReplicaCount must be the observed desired replicas');
424
+ }
425
+ const recordedTasks = readDataElements(runtimeArg.tasks, maximumReplicaCount) ?? [];
426
+ if (recordedTasks.length !== hostObservation.replicas.length
427
+ || hostObservation.replicas.some((replicaArg, indexArg) => {
428
+ const task = recordedTasks[indexArg];
429
+ return !isPlainRecord(task)
430
+ || task.deploymentId !== replicaArg.swarmTaskId
431
+ || task.nodeName !== replicaArg.nodeHostname;
432
+ })) {
433
+ errors.push('runtime.tasks must be the observed replicas: each Swarm task on its node');
434
+ }
435
+ }
152
436
  if (!isPositiveSafeInteger(runtimeArg.requiredReplicaCount, maximumReplicaCount)) {
153
437
  errors.push('runtime.requiredReplicaCount must be a positive bounded replica count');
154
438
  }
@@ -158,7 +442,7 @@ const validateRuntime = (runtimeArg, rootDigestArg) => {
158
442
  if (!isCanonicalDigest(runtimeArg.runtimeDigest)) {
159
443
  errors.push('runtime.runtimeDigest must be a canonical sha256 digest');
160
444
  }
161
- else if (runtimeArg.runtimeDigest !== rootDigestArg) {
445
+ else if (runtimeArg.runtimeDigest !== rootDigest) {
162
446
  errors.push('runtime.runtimeDigest must be the attested source root digest');
163
447
  }
164
448
  if (!Array.isArray(runtimeArg.tasks) || runtimeArg.tasks.length !== runtimeArg.requiredReplicaCount) {
@@ -231,7 +515,7 @@ export const validateLegacyServiceRuntimeAttestation = (attestationArg) => {
231
515
  errors.push('capturedAt must be a positive timestamp');
232
516
  }
233
517
  errors.push(...validateImageSource(data.source));
234
- errors.push(...validateRuntime(data.runtime, isPlainRecord(data.source) ? data.source.rootDigest : undefined));
518
+ errors.push(...validateRuntime(data.runtime, data.source));
235
519
  return errors;
236
520
  };
237
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwbG95bWVudGxlZ2FjeWltcG9ydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvZGVwbG95bWVudGxlZ2FjeWltcG9ydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFLQSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBbUczRSxNQUFNLGVBQWUsR0FBRyxxQ0FBcUMsQ0FBQztBQUM5RCxvR0FBb0c7QUFDcEcsTUFBTSxXQUFXLEdBQUcscUNBQXFDLENBQUM7QUFDMUQsTUFBTSxpQkFBaUIsR0FBRywwREFBMEQsQ0FBQztBQUNyRixNQUFNLGVBQWUsR0FBRyxxRkFBcUYsQ0FBQztBQUM5RyxNQUFNLG1CQUFtQixHQUFHLENBQUMsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDO0FBQzVDLE1BQU0sbUJBQW1CLEdBQUcsRUFBRSxDQUFDO0FBRS9CLE1BQU0scUNBQXFDLEdBQXFEO0lBQzlGLDRDQUE0QztJQUM1QyxzREFBc0Q7Q0FDdkQsQ0FBQztBQUNGLE1BQU0sZ0NBQWdDLEdBQWdEO0lBQ3BGLHlDQUF5QztJQUN6QywyREFBMkQ7Q0FDNUQsQ0FBQztBQUNGLE1BQU0sb0JBQW9CLEdBQTJDLENBQUMsYUFBYSxFQUFFLGFBQWEsQ0FBQyxDQUFDO0FBRXBHLE1BQU0sWUFBWSxHQUFHLENBQUMsUUFBaUIsRUFBc0IsRUFBRSxDQUFDLENBQzlELE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxlQUFlLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUMvRCxDQUFDO0FBRUYsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLFFBQWlCLEVBQTZCLEVBQUUsQ0FBQyxDQUMxRSxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUkscUJBQXFCLENBQUMsUUFBUSxDQUFDLEtBQUssUUFBUSxDQUM3RSxDQUFDO0FBRUYsTUFBTSxx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
521
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwbG95bWVudGxlZ2FjeWltcG9ydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvZGVwbG95bWVudGxlZ2FjeWltcG9ydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFLQSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUM1RCxPQUFPLEVBQ0wsWUFBWSxFQUNaLGFBQWEsRUFDYixnQkFBZ0IsRUFDaEIsa0JBQWtCLEdBQ25CLE1BQU0sOEJBQThCLENBQUM7QUF5THRDLE1BQU0sZUFBZSxHQUFHLHFDQUFxQyxDQUFDO0FBQzlELG9HQUFvRztBQUNwRyxNQUFNLFdBQVcsR0FBRyxxQ0FBcUMsQ0FBQztBQUMxRCxNQUFNLGlCQUFpQixHQUFHLDBEQUEwRCxDQUFDO0FBQ3JGLE1BQU0sZUFBZSxHQUFHLHFGQUFxRixDQUFDO0FBQzlHLE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFDNUMsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLENBQUM7QUFFL0IsTUFBTSxxQ0FBcUMsR0FBcUQ7SUFDOUYsNENBQTRDO0lBQzVDLHNEQUFzRDtDQUN2RCxDQUFDO0FBQ0YsTUFBTSxnQ0FBZ0MsR0FBZ0Q7SUFDcEYseUNBQXlDO0lBQ3pDLDJEQUEyRDtDQUM1RCxDQUFDO0FBQ0YsTUFBTSxvQkFBb0IsR0FBMkMsQ0FBQyxhQUFhLEVBQUUsYUFBYSxDQUFDLENBQUM7QUFDcEcsTUFBTSx5QkFBeUIsR0FBeUM7SUFDdEUsU0FBUztJQUNULFNBQVM7SUFDVCxRQUFRO0lBQ1IsWUFBWTtJQUNaLFVBQVU7SUFDVixRQUFRO0lBQ1IsTUFBTTtDQUNQLENBQUM7QUFDRixNQUFNLDBCQUEwQixHQUEwQztJQUN4RSxTQUFTO0lBQ1QsV0FBVztJQUNYLFVBQVU7SUFDVixNQUFNO0NBQ1AsQ0FBQztBQUNGLHlGQUF5RjtBQUN6RixNQUFNLG9DQUFvQyxHQUEwQyxDQUFDLFNBQVMsRUFB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
@@ -9,7 +9,7 @@ import type { IImageRelease } from '../data/immutableimage.js';
9
9
  import type { IDeploymentPreflightReport, IDeploymentPreflightRequestData } from '../data/deploymentpreflight.js';
10
10
  import type { IDeploymentReservationInput, IServiceDeploymentOperation, IServiceDeploymentStatus } from '../data/deploymentoperation.js';
11
11
  import type { TSha256Digest } from '../data/immutableimage.js';
12
- import type { ILegacyServiceRuntimeAttestation } from '../data/deploymentlegacyimport.js';
12
+ import type { ILegacyServiceHostObservation, ILegacyServiceRuntimeAttestation } from '../data/deploymentlegacyimport.js';
13
13
  import type { IProtocolOffer } from '../protocol.js';
14
14
  import type { IRuntimeAssignmentLogAttemptPage, IRuntimeAssignmentLogPage } from '../data/runtimeassignmentlogs.js';
15
15
  import type { IServiceRuntimeAssignmentView, IServiceRuntimeSpec, IServiceRuntimeStatus, TServiceRuntimeSpecWritableData } from '../data/serviceruntime.js';
@@ -224,10 +224,13 @@ export interface IRequest_Any_Cloudly_CleanupServiceDeployment extends plugins.t
224
224
  };
225
225
  }
226
226
  /**
227
- * Captures durable evidence that a legacy `deployOnPush` service runs exactly the registry root its
228
- * tag names: every required replica observed healthy on that one digest, with the root's platform
229
- * manifests read from Cloudly's own registry. Administrators only, and only while the legacy
230
- * runtime still runs — incomplete evidence is refused, never stored.
227
+ * Captures durable evidence that a legacy `deployOnPush` service runs exactly one registry root of
228
+ * its own repository, from an administrator's observation of its Swarm service and containers on
229
+ * the hosts that run them (`hostObservation`). Cloudly judges the observation itself
230
+ * (`data.judgeLegacyServiceHostObservation`): every desired replica running, healthy or without a
231
+ * healthcheck, on one root that is whole in Cloudly's own registry, its platform manifests read
232
+ * from there. The source tag need not still name that root. Administrators only, and only while
233
+ * the legacy runtime still runs — incomplete evidence is refused, never stored.
231
234
  *
232
235
  * `expectedRuntimeDigest`, when stated, is an assertion the observed digest must equal. Capturing
233
236
  * again for an unchanged digest returns the attestation already stored.
@@ -238,6 +241,7 @@ export interface IRequest_Any_Cloudly_CaptureLegacyServiceRuntimeAttestation ext
238
241
  identity: IIdentityCredential;
239
242
  legacyServiceId: string;
240
243
  expectedRuntimeDigest?: TSha256Digest;
244
+ hostObservation: ILegacyServiceHostObservation;
241
245
  };
242
246
  response: {
243
247
  attestation: ILegacyServiceRuntimeAttestation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.50.0",
3
+ "version": "32.51.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
@@ -30,7 +30,7 @@
30
30
  "@git.zone/tsbuild": "^5.1.2",
31
31
  "@git.zone/tsdoc": "^3.0.2",
32
32
  "@git.zone/tsrun": "^3.0.2",
33
- "@git.zone/tstest": "^7.1.0",
33
+ "@git.zone/tstest": "^7.2.0",
34
34
  "@types/node": "26.6.3"
35
35
  },
36
36
  "files": [
package/readme.md CHANGED
@@ -3640,7 +3640,7 @@ The immutable deployment workflow is:
3640
3640
 
3641
3641
  A legacy `deployOnPush` service without deployable source moves to a greenfield successor by a legacy import, which replaces step 2:
3642
3642
 
3643
- - While the legacy replicas still run — before its runtime is frozen — an administrator captures the evidence with `captureLegacyServiceRuntimeAttestation` (`requests.service.IRequest_Any_Cloudly_CaptureLegacyServiceRuntimeAttestation`). The stored `data.ILegacyServiceRuntimeAttestation` records every required replica healthy on one digest, the registry root the legacy tag names, and that root's platform manifests; `data.validateLegacyServiceRuntimeAttestation` states its rules. Incomplete evidence is refused, never stored, and there is no substitute once the runtime is stopped. `getLegacyServiceRuntimeAttestations` lists what Cloudly holds for a legacy service; both requests are for administrators only.
3643
+ - While the legacy replicas still run — before its runtime is frozen — an administrator reads the legacy Swarm service and every container of its tasks on the hosts that run them, and submits that as `hostObservation` (`data.ILegacyServiceHostObservation`: Swarm service name, spec image and mode with its desired replicas; per replica the node hostname, Swarm task id, container id, the engine's image store, the container's image id, the image's `RepoDigests`, the container state and its health, `none` when no healthcheck applies) to `captureLegacyServiceRuntimeAttestation` (`requests.service.IRequest_Any_Cloudly_CaptureLegacyServiceRuntimeAttestation`). `data.validateLegacyServiceHostObservation` judges its shape, `data.selectLegacyServiceHostObservationRootDigest` names the one root every replica runs from the service's own repository, and `data.judgeLegacyServiceHostObservation` judges it against that root as Cloudly reads it from its own registry: exactly the desired replicas of a replicated service, its spec image naming the repository by the source tag or the root digest, every replica `running` and `healthy` or `none`, each container's image id the root digest on the containerd image store or one of the root's platform image configuration digests on the classic store. The source tag need not still name the root. The stored `data.ILegacyServiceRuntimeAttestation` records that root, its platform manifests, the replicas, the evidence source `administrator-host-observation` and the observation itself; `data.validateLegacyServiceRuntimeAttestation` states its rules. Incomplete evidence is refused, never stored, and there is no substitute once the runtime is stopped. `getLegacyServiceRuntimeAttestations` lists what Cloudly holds for a legacy service; both requests are for administrators only.
3644
3644
  - The greenfield reservation, and every preflight of it, names `legacyImport` (`data.ILegacyImageImportReference`: legacy service, attestation, asserted source root digest) instead of `sourceRevision`, `sourceDirty` and release evidence. Refusals are the blockers `LEGACY_IMPORT_ATTESTATION_MISSING`, `LEGACY_IMPORT_SOURCE_MISMATCH` and `LEGACY_IMPORT_PLATFORM_MISMATCH`.
3645
3645
  - Instead of the registry push, the deployer calls `importServiceDeploymentLegacyImage`: Cloudly imports the attested image from its own registry into the reserved tag — an index root as it is, a single platform manifest under a one-descriptor index of the manifest's own family — and records release evidence of source `cloudly-legacy-import` naming the legacy source. Steps 3 to 5 follow unchanged; `selectTrustedImageReleaseEvidence` accepts that evidence only with `evidenceSource: 'cloudly-legacy-import'`.
3646
3646
  - The reservation and the import need the capability `deployment:import-legacy`, which only an organization-service-slot grant can carry.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.50.0',
6
+ version: '32.51.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -4,15 +4,22 @@ import type {
4
4
  TSha256Digest,
5
5
  } from './immutableimage.js';
6
6
  import { normalizeSha256Digest } from './immutableimage.js';
7
- import { hasExactKeys, isPlainRecord } from '../private/dataproperties.js';
7
+ import {
8
+ hasExactKeys,
9
+ isPlainRecord,
10
+ readDataElements,
11
+ readDataProperties,
12
+ } from '../private/dataproperties.js';
8
13
 
9
14
  /**
10
15
  * Legacy image import: a greenfield deployment whose image is the one a legacy `deployOnPush`
11
16
  * service already runs, instead of one built from a source revision.
12
17
  *
13
18
  * A legacy service without deployable source still has an image in Cloudly's registry and, until
14
- * its runtime is frozen, healthy replicas running it. An administrator captures that fact once as
15
- * a {@link ILegacyServiceRuntimeAttestation} while the replicas still run. A greenfield successor
19
+ * its runtime is frozen, replicas running it. An administrator observes those replicas on the
20
+ * hosts that run them ({@link ILegacyServiceHostObservation}) while they still run, and Cloudly
21
+ * captures that observation once as a {@link ILegacyServiceRuntimeAttestation} after it verified
22
+ * the observed root against its own registry. A greenfield successor
16
23
  * reservation names the attestation in `legacyImport` instead of a `sourceRevision`, and Cloudly
17
24
  * then imports the attested image into the successor's own repository and release tag itself:
18
25
  * the bytes never leave the registry and the release records that it was imported, not built.
@@ -60,19 +67,98 @@ export interface ILegacyServiceImageSource {
60
67
  platforms: ILegacyImagePlatformManifest[];
61
68
  }
62
69
 
63
- /** One healthy replica of the legacy service, observed running the attested digest. */
70
+ /**
71
+ * One replica of the legacy service, observed running the attested digest in its own stated
72
+ * health: healthy, or running with no healthcheck that could state one.
73
+ */
64
74
  export interface ILegacyServiceRuntimeTaskAttestation {
75
+ /** The replica's runtime identity: the Swarm task it was observed as. */
65
76
  deploymentId: string;
77
+ /** The hostname of the node the replica was observed on. */
66
78
  nodeName: string;
67
79
  observedDigest: TSha256Digest;
68
80
  }
69
81
 
82
+ /**
83
+ * Where the runtime part of an attestation comes from. `administrator-host-observation`: an
84
+ * administrator read the legacy Swarm service and its containers on the hosts that run them, and
85
+ * Cloudly judged that observation against its own registry.
86
+ */
87
+ export type TLegacyServiceRuntimeEvidenceSource = 'administrator-host-observation';
88
+
89
+ /** A container's state as the Docker engine reports it (`State.Status`). */
90
+ export type TLegacyHostContainerState =
91
+ | 'created'
92
+ | 'running'
93
+ | 'paused'
94
+ | 'restarting'
95
+ | 'removing'
96
+ | 'exited'
97
+ | 'dead';
98
+
99
+ /**
100
+ * A container's health as the Docker engine reports it (`State.Health.Status`). `none`: no
101
+ * healthcheck applies to the container — the image declares none and the service adds none — so
102
+ * the engine reports no health state at all.
103
+ */
104
+ export type TLegacyHostContainerHealth = 'healthy' | 'unhealthy' | 'starting' | 'none';
105
+
106
+ /**
107
+ * The image store of the Docker engine that runs a container. On the `containerd` store a
108
+ * container's image id is the digest of the root it was pulled as; on the `classic` store it is
109
+ * the image configuration digest of the platform manifest the engine pulled.
110
+ */
111
+ export type TLegacyHostImageStore = 'containerd' | 'classic';
112
+
113
+ /** A Swarm service's mode (`Spec.Mode`): a replicated service states its desired replicas. */
114
+ export type TLegacyHostSwarmServiceMode =
115
+ | { kind: 'replicated'; desiredReplicas: number }
116
+ | { kind: 'global' };
117
+
118
+ /** One replica of a legacy Swarm service, as an administrator read it on the node that runs it. */
119
+ export interface ILegacyHostReplicaObservation {
120
+ /** The hostname of the node the task runs on. */
121
+ nodeHostname: string;
122
+ /** The Swarm task id (25 lowercase base-36 characters). */
123
+ swarmTaskId: string;
124
+ /** The full container id of the task (64 lowercase hex characters). */
125
+ containerId: string;
126
+ imageStore: TLegacyHostImageStore;
127
+ /** The container's image id (`Image`), a sha256 digest. */
128
+ containerImageId: TSha256Digest;
129
+ /** The `RepoDigests` of the container's image, exactly as the engine lists them. */
130
+ imageRepoDigests: string[];
131
+ state: TLegacyHostContainerState;
132
+ health: TLegacyHostContainerHealth;
133
+ }
134
+
135
+ /**
136
+ * An administrator's observation of the legacy Swarm service of one legacy service and of every
137
+ * container its tasks run, read on the hosts while the legacy runtime still runs.
138
+ *
139
+ * It states facts, not conclusions: Cloudly judges it against its own registry
140
+ * ({@link judgeLegacyServiceHostObservation}) before it attests anything.
141
+ */
142
+ export interface ILegacyServiceHostObservation {
143
+ /** The Swarm service name. */
144
+ swarmServiceName: string;
145
+ /** The image reference of the Swarm service spec (`Spec.TaskTemplate.ContainerSpec.Image`). */
146
+ specImage: string;
147
+ mode: TLegacyHostSwarmServiceMode;
148
+ /** When the administrator read the hosts, in milliseconds since the epoch. */
149
+ observedAt: number;
150
+ /** Every running task's replica, sorted by `swarmTaskId`. */
151
+ replicas: ILegacyHostReplicaObservation[];
152
+ }
153
+
70
154
  /**
71
155
  * Durable evidence, captured by an administrator while the legacy runtime still ran, that every
72
- * required replica of a legacy service was healthy and ran exactly the registry root it names.
156
+ * desired replica of a legacy service ran, in its own stated health, exactly the registry root it
157
+ * names.
73
158
  *
74
159
  * Cloudly writes one and never changes it. A capture only succeeds on complete evidence, so a
75
- * stored attestation is by construction one whose runtime digest is its source root digest.
160
+ * stored attestation is by construction one whose runtime digest is its source root digest and
161
+ * whose runtime part is exactly the host observation it records.
76
162
  */
77
163
  export interface ILegacyServiceRuntimeAttestation {
78
164
  id: string;
@@ -81,11 +167,16 @@ export interface ILegacyServiceRuntimeAttestation {
81
167
  organizationId: string;
82
168
  source: ILegacyServiceImageSource;
83
169
  runtime: {
170
+ /** The desired replicas of the observed replicated Swarm service. */
84
171
  requiredReplicaCount: number;
172
+ /** Replicas observed running healthy, or running with no healthcheck (`health: 'none'`). */
85
173
  healthyReplicaCount: number;
86
174
  runtimeDigest: TSha256Digest;
87
- /** Sorted by deploymentId, one per required replica. */
175
+ /** Sorted by deploymentId, one per required replica: the observed replicas. */
88
176
  tasks: ILegacyServiceRuntimeTaskAttestation[];
177
+ evidenceSource: TLegacyServiceRuntimeEvidenceSource;
178
+ /** The observation the runtime part was built from, as the administrator submitted it. */
179
+ hostObservation: ILegacyServiceHostObservation;
89
180
  };
90
181
  capturedByUserId: string;
91
182
  capturedAt: number;
@@ -120,6 +211,31 @@ const legacyImageImportIndexMediaTypes: readonly TLegacyImageImportIndexMediaTyp
120
211
  'application/vnd.docker.distribution.manifest.list.v2+json',
121
212
  ];
122
213
  const legacyImagePlatforms: readonly TImmutableContainerPlatform[] = ['linux/amd64', 'linux/arm64'];
214
+ const legacyHostContainerStates: readonly TLegacyHostContainerState[] = [
215
+ 'created',
216
+ 'running',
217
+ 'paused',
218
+ 'restarting',
219
+ 'removing',
220
+ 'exited',
221
+ 'dead',
222
+ ];
223
+ const legacyHostContainerHealths: readonly TLegacyHostContainerHealth[] = [
224
+ 'healthy',
225
+ 'unhealthy',
226
+ 'starting',
227
+ 'none',
228
+ ];
229
+ /** The health states a replica may be attested in: healthy, or no healthcheck at all. */
230
+ const attestableLegacyHostContainerHealths: readonly TLegacyHostContainerHealth[] = ['healthy', 'none'];
231
+ const legacyHostImageStores: readonly TLegacyHostImageStore[] = ['containerd', 'classic'];
232
+ const swarmTaskIdRegex = /^[a-z0-9]{25}$/;
233
+ const containerIdRegex = /^[0-9a-f]{64}$/;
234
+ const swarmServiceNameRegex = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
235
+ const nodeHostnameRegex = /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
236
+ const imageReferenceRegex = /^\S{1,512}$/;
237
+ const repoDigestRegex = /^[^\s@]{1,440}@sha256:[0-9a-f]{64}$/;
238
+ const maximumRepoDigestCount = 32;
123
239
 
124
240
  const isIdentifier = (valueArg: unknown): valueArg is string => (
125
241
  typeof valueArg === 'string' && identifierRegex.test(valueArg)
@@ -168,6 +284,256 @@ export const validateLegacyImageImportReference = (referenceArg: unknown): strin
168
284
  return errors;
169
285
  };
170
286
 
287
+ const validateHostReplicaObservation = (
288
+ replicaArg: unknown,
289
+ labelArg: string,
290
+ ): string[] => {
291
+ const replica = readDataProperties(replicaArg);
292
+ if (replica === null) return [`${labelArg} must be a plain data object`];
293
+ const errors: string[] = [];
294
+ if (!hasExactKeys(replica, [
295
+ 'nodeHostname',
296
+ 'swarmTaskId',
297
+ 'containerId',
298
+ 'imageStore',
299
+ 'containerImageId',
300
+ 'imageRepoDigests',
301
+ 'state',
302
+ 'health',
303
+ ])) {
304
+ errors.push(`${labelArg} must use its exact schema`);
305
+ }
306
+ if (typeof replica.nodeHostname !== 'string' || !nodeHostnameRegex.test(replica.nodeHostname)) {
307
+ errors.push(`${labelArg}.nodeHostname must be a host name`);
308
+ }
309
+ if (typeof replica.swarmTaskId !== 'string' || !swarmTaskIdRegex.test(replica.swarmTaskId)) {
310
+ errors.push(`${labelArg}.swarmTaskId must be a Swarm task id`);
311
+ }
312
+ if (typeof replica.containerId !== 'string' || !containerIdRegex.test(replica.containerId)) {
313
+ errors.push(`${labelArg}.containerId must be a full container id`);
314
+ }
315
+ if (!legacyHostImageStores.some((storeArg) => storeArg === replica.imageStore)) {
316
+ errors.push(`${labelArg}.imageStore must be containerd or classic`);
317
+ }
318
+ if (!isCanonicalDigest(replica.containerImageId)) {
319
+ errors.push(`${labelArg}.containerImageId must be a canonical sha256 digest`);
320
+ }
321
+ const repoDigests = readDataElements(replica.imageRepoDigests, maximumRepoDigestCount);
322
+ if (repoDigests === null
323
+ || repoDigests.some((entryArg) => typeof entryArg !== 'string' || !repoDigestRegex.test(entryArg))
324
+ || new Set(repoDigests).size !== repoDigests.length) {
325
+ errors.push(`${labelArg}.imageRepoDigests must be unique digested image references`);
326
+ }
327
+ if (!legacyHostContainerStates.some((stateArg) => stateArg === replica.state)) {
328
+ errors.push(`${labelArg}.state must be a Docker container state`);
329
+ }
330
+ if (!legacyHostContainerHealths.some((healthArg) => healthArg === replica.health)) {
331
+ errors.push(`${labelArg}.health must be healthy, unhealthy, starting or none`);
332
+ }
333
+ return errors;
334
+ };
335
+
336
+ const validateHostSwarmServiceMode = (modeArg: unknown, labelArg: string): string[] => {
337
+ const mode = readDataProperties(modeArg);
338
+ if (mode === null) return [`${labelArg} must be a plain data object`];
339
+ if (mode.kind === 'global') {
340
+ return hasExactKeys(mode, ['kind']) ? [] : [`${labelArg} must use its exact schema`];
341
+ }
342
+ if (mode.kind !== 'replicated') return [`${labelArg}.kind must be replicated or global`];
343
+ const errors: string[] = [];
344
+ if (!hasExactKeys(mode, ['kind', 'desiredReplicas'])) {
345
+ errors.push(`${labelArg} must use its exact schema`);
346
+ }
347
+ if (typeof mode.desiredReplicas !== 'number'
348
+ || !Number.isSafeInteger(mode.desiredReplicas)
349
+ || mode.desiredReplicas < 0
350
+ || mode.desiredReplicas > maximumReplicaCount) {
351
+ errors.push(`${labelArg}.desiredReplicas must be a bounded replica count`);
352
+ }
353
+ return errors;
354
+ };
355
+
356
+ /**
357
+ * The shape of a host observation: exact keys at every level, Docker's own value sets, at most 64
358
+ * replicas, unique and sorted by Swarm task id, each container observed once. It judges nothing
359
+ * against a registry; {@link judgeLegacyServiceHostObservation} does.
360
+ */
361
+ export const validateLegacyServiceHostObservation = (
362
+ observationArg: unknown,
363
+ labelArg = 'hostObservation',
364
+ ): string[] => {
365
+ const observation = readDataProperties(observationArg);
366
+ if (observation === null) return [`${labelArg} must be a plain data object`];
367
+ const errors: string[] = [];
368
+ if (!hasExactKeys(observation, ['swarmServiceName', 'specImage', 'mode', 'observedAt', 'replicas'])) {
369
+ errors.push(`${labelArg} must use its exact schema`);
370
+ }
371
+ if (typeof observation.swarmServiceName !== 'string'
372
+ || !swarmServiceNameRegex.test(observation.swarmServiceName)) {
373
+ errors.push(`${labelArg}.swarmServiceName must be a Swarm service name`);
374
+ }
375
+ if (typeof observation.specImage !== 'string' || !imageReferenceRegex.test(observation.specImage)) {
376
+ errors.push(`${labelArg}.specImage must be a bounded image reference`);
377
+ }
378
+ errors.push(...validateHostSwarmServiceMode(observation.mode, `${labelArg}.mode`));
379
+ if (!isPositiveSafeInteger(observation.observedAt, Number.MAX_SAFE_INTEGER)) {
380
+ errors.push(`${labelArg}.observedAt must be a positive timestamp`);
381
+ }
382
+ const replicas = readDataElements(observation.replicas, maximumReplicaCount);
383
+ if (replicas === null) {
384
+ errors.push(`${labelArg}.replicas must be a list of at most ${maximumReplicaCount} replicas`);
385
+ return errors;
386
+ }
387
+ replicas.forEach((replicaArg, indexArg) => {
388
+ errors.push(...validateHostReplicaObservation(replicaArg, `${labelArg}.replicas[${indexArg}]`));
389
+ });
390
+ const readReplicaKey = (replicaArg: unknown, keyArg: string): unknown => (
391
+ readDataProperties(replicaArg)?.[keyArg]
392
+ );
393
+ const taskIds = replicas.map((replicaArg) => readReplicaKey(replicaArg, 'swarmTaskId'));
394
+ const sortedTaskIds = taskIds
395
+ .filter((idArg): idArg is string => typeof idArg === 'string')
396
+ .sort();
397
+ if (new Set(taskIds).size !== taskIds.length
398
+ || JSON.stringify(taskIds) !== JSON.stringify(sortedTaskIds)) {
399
+ errors.push(`${labelArg}.replicas must be unique and sorted by swarmTaskId`);
400
+ }
401
+ const containerIds = replicas.map((replicaArg) => readReplicaKey(replicaArg, 'containerId'));
402
+ if (new Set(containerIds).size !== containerIds.length) {
403
+ errors.push(`${labelArg}.replicas must observe each container once`);
404
+ }
405
+ return errors;
406
+ };
407
+
408
+ /** True when a value is a well-formed host observation. */
409
+ export const isLegacyServiceHostObservation = (
410
+ valueArg: unknown,
411
+ ): valueArg is ILegacyServiceHostObservation => (
412
+ validateLegacyServiceHostObservation(valueArg).length === 0
413
+ );
414
+
415
+ /**
416
+ * The root digest one replica was pulled as from `registryHost/repository`.
417
+ *
418
+ * On the containerd store that is the container's image id, which the image's `RepoDigests` must
419
+ * list for the repository. On the classic store it is the one digest `RepoDigests` lists for the
420
+ * repository; none, or more than one, leaves the root unknown.
421
+ */
422
+ const selectReplicaRootDigest = (
423
+ replicaArg: ILegacyHostReplicaObservation,
424
+ repositoryReferenceArg: string,
425
+ ): TSha256Digest | undefined => {
426
+ const prefix = `${repositoryReferenceArg}@`;
427
+ const repositoryDigests = replicaArg.imageRepoDigests
428
+ .filter((entryArg) => entryArg.startsWith(prefix))
429
+ .map((entryArg) => normalizeSha256Digest(entryArg.slice(prefix.length)))
430
+ .filter((digestArg): digestArg is TSha256Digest => digestArg !== undefined);
431
+ if (replicaArg.imageStore === 'containerd') {
432
+ return repositoryDigests.includes(replicaArg.containerImageId)
433
+ ? replicaArg.containerImageId
434
+ : undefined;
435
+ }
436
+ return repositoryDigests.length === 1 ? repositoryDigests[0] : undefined;
437
+ };
438
+
439
+ /**
440
+ * The one root digest every observed replica runs from the legacy service's own repository, which
441
+ * is the digest Cloudly then inspects in its registry. Refused when a replica's root from that
442
+ * repository is unknown, when the replicas run more than one root, or when nothing was observed.
443
+ */
444
+ export const selectLegacyServiceHostObservationRootDigest = (argsArg: {
445
+ observation: ILegacyServiceHostObservation;
446
+ registryHost: string;
447
+ repository: string;
448
+ }): { rootDigest?: TSha256Digest; errors: string[] } => {
449
+ const repositoryReference = `${argsArg.registryHost}/${argsArg.repository}`;
450
+ const errors: string[] = [];
451
+ const roots = new Set<TSha256Digest>();
452
+ argsArg.observation.replicas.forEach((replicaArg, indexArg) => {
453
+ const root = selectReplicaRootDigest(replicaArg, repositoryReference);
454
+ if (root === undefined) {
455
+ errors.push(replicaArg.imageStore === 'containerd'
456
+ ? `hostObservation.replicas[${indexArg}] image is not listed as ${repositoryReference}@<its image id>`
457
+ : `hostObservation.replicas[${indexArg}] image lists not exactly one digest of ${repositoryReference}`);
458
+ return;
459
+ }
460
+ roots.add(root);
461
+ });
462
+ if (argsArg.observation.replicas.length === 0) {
463
+ errors.push('hostObservation must observe at least one replica');
464
+ }
465
+ if (roots.size > 1) {
466
+ errors.push('hostObservation replicas run more than one root digest');
467
+ }
468
+ const [rootDigest] = [...roots];
469
+ return errors.length === 0 && rootDigest !== undefined ? { rootDigest, errors } : { errors };
470
+ };
471
+
472
+ /**
473
+ * Judges a well-formed host observation against the registry source Cloudly inspected for it.
474
+ * Every rule an attestation's runtime part must meet:
475
+ *
476
+ * - the Swarm service is replicated, with at least one desired replica, and exactly its desired
477
+ * replicas were observed;
478
+ * - its spec image names the source repository by the source tag, by the root digest, or by both;
479
+ * - every replica is `running`, and `healthy` or without a healthcheck (`none`) — `unhealthy` and
480
+ * `starting` are refused;
481
+ * - every replica runs the source root from the source repository: its image lists
482
+ * `registryHost/repository@rootDigest`, and its image id is the root digest on the containerd
483
+ * store or one of the root's platform image configuration digests on the classic store.
484
+ */
485
+ export const judgeLegacyServiceHostObservation = (argsArg: {
486
+ observation: ILegacyServiceHostObservation;
487
+ source: ILegacyServiceImageSource;
488
+ label?: string;
489
+ }): string[] => {
490
+ const label = argsArg.label ?? 'hostObservation';
491
+ const { observation, source } = argsArg;
492
+ const errors: string[] = [];
493
+ if (observation.mode.kind !== 'replicated') {
494
+ errors.push(`${label}.mode must be replicated: a legacy service runs as a replicated Swarm service`);
495
+ } else if (observation.mode.desiredReplicas < 1) {
496
+ errors.push(`${label}.mode must desire at least one replica`);
497
+ } else if (observation.replicas.length !== observation.mode.desiredReplicas) {
498
+ errors.push(`${label} must observe exactly the ${observation.mode.desiredReplicas} desired replicas`);
499
+ }
500
+ const repositoryReference = `${source.registryHost}/${source.repository}`;
501
+ const specImages = [
502
+ `${repositoryReference}:${source.tag}`,
503
+ `${repositoryReference}@${source.rootDigest}`,
504
+ `${repositoryReference}:${source.tag}@${source.rootDigest}`,
505
+ ];
506
+ if (!specImages.includes(observation.specImage)) {
507
+ errors.push(`${label}.specImage must name ${repositoryReference} by its source tag or root digest`);
508
+ }
509
+ const imageConfigDigests = source.platforms.map((platformArg) => platformArg.imageConfigDigest);
510
+ observation.replicas.forEach((replicaArg, indexArg) => {
511
+ const replicaLabel = `${label}.replicas[${indexArg}]`;
512
+ if (replicaArg.state !== 'running') {
513
+ errors.push(`${replicaLabel}.state must be running`);
514
+ }
515
+ if (!attestableLegacyHostContainerHealths.includes(replicaArg.health)) {
516
+ errors.push(`${replicaLabel}.health must be healthy or none`);
517
+ }
518
+ if (!replicaArg.imageRepoDigests.includes(`${repositoryReference}@${source.rootDigest}`)) {
519
+ errors.push(`${replicaLabel} image must be ${repositoryReference}@${source.rootDigest}`);
520
+ }
521
+ if (replicaArg.imageStore === 'containerd') {
522
+ if (replicaArg.containerImageId !== source.rootDigest) {
523
+ errors.push(`${replicaLabel}.containerImageId must be the root digest on the containerd image store`);
524
+ }
525
+ return;
526
+ }
527
+ if (!imageConfigDigests.includes(replicaArg.containerImageId)) {
528
+ errors.push(`${replicaLabel}.containerImageId must be a platform image configuration digest of the root on the classic image store`);
529
+ }
530
+ if (selectReplicaRootDigest(replicaArg, repositoryReference) === undefined) {
531
+ errors.push(`${replicaLabel} image must list exactly one digest of ${repositoryReference} on the classic image store`);
532
+ }
533
+ });
534
+ return errors;
535
+ };
536
+
171
537
  const validatePlatformManifest = (
172
538
  manifestArg: unknown,
173
539
  indexArg: number,
@@ -270,17 +636,54 @@ const validateImageSource = (sourceArg: unknown): string[] => {
270
636
  return errors;
271
637
  };
272
638
 
273
- const validateRuntime = (runtimeArg: unknown, rootDigestArg: unknown): string[] => {
639
+ const isLegacyServiceImageSource = (valueArg: unknown): valueArg is ILegacyServiceImageSource => (
640
+ validateImageSource(valueArg).length === 0
641
+ );
642
+
643
+ const validateRuntime = (runtimeArg: unknown, sourceArg: unknown): string[] => {
274
644
  if (!isPlainRecord(runtimeArg)) return ['runtime must be an object'];
645
+ const rootDigest = isPlainRecord(sourceArg) ? sourceArg.rootDigest : undefined;
275
646
  const errors: string[] = [];
276
647
  if (!hasExactKeys(runtimeArg, [
277
648
  'requiredReplicaCount',
278
649
  'healthyReplicaCount',
279
650
  'runtimeDigest',
280
651
  'tasks',
652
+ 'evidenceSource',
653
+ 'hostObservation',
281
654
  ])) {
282
655
  errors.push('runtime must use its exact schema');
283
656
  }
657
+ if (runtimeArg.evidenceSource !== 'administrator-host-observation') {
658
+ errors.push('runtime.evidenceSource must be administrator-host-observation');
659
+ }
660
+ const hostObservation = runtimeArg.hostObservation;
661
+ const observationErrors = validateLegacyServiceHostObservation(
662
+ hostObservation, 'runtime.hostObservation');
663
+ errors.push(...observationErrors);
664
+ if (observationErrors.length === 0
665
+ && isLegacyServiceHostObservation(hostObservation)
666
+ && isLegacyServiceImageSource(sourceArg)) {
667
+ errors.push(...judgeLegacyServiceHostObservation({
668
+ observation: hostObservation,
669
+ source: sourceArg,
670
+ label: 'runtime.hostObservation',
671
+ }));
672
+ if (hostObservation.mode.kind === 'replicated'
673
+ && runtimeArg.requiredReplicaCount !== hostObservation.mode.desiredReplicas) {
674
+ errors.push('runtime.requiredReplicaCount must be the observed desired replicas');
675
+ }
676
+ const recordedTasks = readDataElements(runtimeArg.tasks, maximumReplicaCount) ?? [];
677
+ if (recordedTasks.length !== hostObservation.replicas.length
678
+ || hostObservation.replicas.some((replicaArg, indexArg) => {
679
+ const task = recordedTasks[indexArg];
680
+ return !isPlainRecord(task)
681
+ || task.deploymentId !== replicaArg.swarmTaskId
682
+ || task.nodeName !== replicaArg.nodeHostname;
683
+ })) {
684
+ errors.push('runtime.tasks must be the observed replicas: each Swarm task on its node');
685
+ }
686
+ }
284
687
  if (!isPositiveSafeInteger(runtimeArg.requiredReplicaCount, maximumReplicaCount)) {
285
688
  errors.push('runtime.requiredReplicaCount must be a positive bounded replica count');
286
689
  }
@@ -289,7 +692,7 @@ const validateRuntime = (runtimeArg: unknown, rootDigestArg: unknown): string[]
289
692
  }
290
693
  if (!isCanonicalDigest(runtimeArg.runtimeDigest)) {
291
694
  errors.push('runtime.runtimeDigest must be a canonical sha256 digest');
292
- } else if (runtimeArg.runtimeDigest !== rootDigestArg) {
695
+ } else if (runtimeArg.runtimeDigest !== rootDigest) {
293
696
  errors.push('runtime.runtimeDigest must be the attested source root digest');
294
697
  }
295
698
  if (!Array.isArray(runtimeArg.tasks) || runtimeArg.tasks.length !== runtimeArg.requiredReplicaCount) {
@@ -363,9 +766,6 @@ export const validateLegacyServiceRuntimeAttestation = (attestationArg: unknown)
363
766
  errors.push('capturedAt must be a positive timestamp');
364
767
  }
365
768
  errors.push(...validateImageSource(data.source));
366
- errors.push(...validateRuntime(
367
- data.runtime,
368
- isPlainRecord(data.source) ? data.source.rootDigest : undefined,
369
- ));
769
+ errors.push(...validateRuntime(data.runtime, data.source));
370
770
  return errors;
371
771
  };
@@ -17,7 +17,10 @@ import type {
17
17
  IServiceDeploymentStatus,
18
18
  } from '../data/deploymentoperation.js';
19
19
  import type { TSha256Digest } from '../data/immutableimage.js';
20
- import type { ILegacyServiceRuntimeAttestation } from '../data/deploymentlegacyimport.js';
20
+ import type {
21
+ ILegacyServiceHostObservation,
22
+ ILegacyServiceRuntimeAttestation,
23
+ } from '../data/deploymentlegacyimport.js';
21
24
  import type { IProtocolOffer } from '../protocol.js';
22
25
  import type {
23
26
  IRuntimeAssignmentLogAttemptPage,
@@ -330,10 +333,13 @@ extends plugins.typedrequestInterfaces.implementsTR<
330
333
  }
331
334
 
332
335
  /**
333
- * Captures durable evidence that a legacy `deployOnPush` service runs exactly the registry root its
334
- * tag names: every required replica observed healthy on that one digest, with the root's platform
335
- * manifests read from Cloudly's own registry. Administrators only, and only while the legacy
336
- * runtime still runs — incomplete evidence is refused, never stored.
336
+ * Captures durable evidence that a legacy `deployOnPush` service runs exactly one registry root of
337
+ * its own repository, from an administrator's observation of its Swarm service and containers on
338
+ * the hosts that run them (`hostObservation`). Cloudly judges the observation itself
339
+ * (`data.judgeLegacyServiceHostObservation`): every desired replica running, healthy or without a
340
+ * healthcheck, on one root that is whole in Cloudly's own registry, its platform manifests read
341
+ * from there. The source tag need not still name that root. Administrators only, and only while
342
+ * the legacy runtime still runs — incomplete evidence is refused, never stored.
337
343
  *
338
344
  * `expectedRuntimeDigest`, when stated, is an assertion the observed digest must equal. Capturing
339
345
  * again for an unchanged digest returns the attestation already stored.
@@ -348,6 +354,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
348
354
  identity: IIdentityCredential;
349
355
  legacyServiceId: string;
350
356
  expectedRuntimeDigest?: TSha256Digest;
357
+ hostObservation: ILegacyServiceHostObservation;
351
358
  };
352
359
  response: {
353
360
  attestation: ILegacyServiceRuntimeAttestation;