Skip to main content

Artifact Package Manifest

Schema id: artifact-package@1 Machine-readable schema: artifact-package.schema.json File name inside the archive: parsware.json, at its root

What turns a folder of bundler output into something the platform can accept. It is written by npm run pack beside the build output and zipped with it; the platform reads it at upload, and a hand-assembled archive is refused by exactly the same rule as a generated one (the one-validation-path principle, Section 6.7).

Why it exists​

Registration used to ask a maker for six fields, two of which were copied out of a bundler config: the Module Federation scope and the exposed module. Getting either wrong produced one symptom — the control did not load — with nothing to say which of the two was wrong. The build already knows both, so it writes them down.

Shape​

FieldTypeRequiredNotes
formatVersionintegeryesMust be exactly 1. A const rather than a minimum, so an archive from a future format is refused with a clear message instead of read under today's rules and failing somewhere less obvious.
kindstringyesCustomControl. Custom Apps join this enum when they adopt the package — they will use the same store, cache and serving endpoint, and differ only in this manifest's per-kind fields.
logicalNamestringyesPublisher-prefixed, lower snake-case (e.g. par_signature_pad). Checked against the owning solution's publisher prefix at upload, so a prefix cannot be claimed by an archive from elsewhere.
displayNamestringyesHuman-readable name shown in the form designer's field palette.
descriptionstring | nullnoShort description, max 512 characters.
versionstringyesThree-part version (1.2.3), the control's own npm version. Recorded for diagnostics and shown to a maker checking that what is installed is what they built; it gates nothing.
scopestringyesThe Module Federation container scope, read from the build's federation config.
modulestringyesThe exposed module path (e.g. ./Control), read from the federation config's exposes map.
entrystringyesThe container entry file inside the archive (e.g. remoteEntry.js). A bare file name, not a path: it names a file at the archive root, which is also the base path the platform serves the artifact's async chunks from.
substitutesForstring[]yes for CustomControlThe field data-type families the control can be bound to. Must match custom-control-manifest@1's enum exactly.
requestedScopesstring[]yes for CustomAppThe OAuth scopes a Custom App declares it needs, requested when its App Context Token is minted (§6.3). Absent for a Custom Control, which declares none — it runs with the permissions of the person using it (§28).

Where the values come from​

Two sources, and neither guesses at the other's half:

  • scope, module, entry are read from the build's own mf-manifest.json, because the bundler decides them. @module-federation/vite emits that file only when configured with manifest: true, which is off by default — so every Parsware control turns it on, and the packer refuses to guess if it is missing.
  • logicalName, displayName, description, substitutesFor come from a parsware block in the control's package.json, because the bundler has never heard of any of them. version is that file's own version.

What is deliberately absent​

There are no declared scopes and no declared entities. An artifact runs with the permissions of the person using it (§24), so a scope list on the artifact could only ever narrow its user, never widen them.

There is no remote-entry URL. The package is served from the platform's own origin at a path computed from the environment and the logical name, which is what lets a control travel inside its solution instead of being fetched from an external host.

What the platform checks​

At upload, before anything is stored:

  1. the archive is within the compressed size cap and is a readable zip;
  2. it holds no more than the configured number of files, and expands to no more than the configured total (checked while reading, and again while extracting — a zip header's declared length is a claim the archive makes about itself);
  3. every entry path is relative and safe — .., a rooted path, and a Windows drive or UNC prefix are rejected rather than sanitized, because an archive naming a path outside its own folder is not one to quietly repair;
  4. every file has an extension on the platform's serve allow-list;
  5. parsware.json is present at the root and validates against this schema;
  6. the kind matches the endpoint, and the file named by entry is actually present.