Part IV. Serialisation

AFDS specification, version 1.0.0

Parts I to III say what a design system must declare about itself. This part says how those declarations travel.

A package is a single file, because a design system that arrives as a directory to be assembled arrives differently for each recipient, and a contract that is reassembled is a contract that can be reassembled wrongly. Everything here follows from wanting one file that a consumer can verify before trusting a word of its contents.

Clause 25 defines the container and clause 26 the media type. Clause 27 fixes where each kind of artefact lives, and clause 28 defines the six roles an artefact can hold and the ownership rule that keeps the accessibility contract inside the package. Clause 29 specifies the root manifest, including the bindings Parts I to III defer to this part: the method profile array, the target conformance level, a locally defined profile and its provenance object, and the pattern registry. Clause 30 specifies the root inventory and clause 31 the algorithm that verifies a package against it. Clause 32 states the security requirements, which exist because a package is an archive and an archive is an attack surface. Clause 33 specifies adapters, in both directions. Clause 34 defines the completeness profiles and clause 35 the versioning rules.

Two things this part deliberately does not do.

It does not define a signature format, and clause 32.3 says why: a digest establishes that a package is intact, not that it came from anyone in particular, and a format that blurred the two would let a reader mistake integrity for authenticity. It does not define an adapter for any particular external target. Clause 33 specifies what an adapter must declare and report, and no adapter is canonical in either direction, because the moment one is, the format has a preferred toolchain and the portability claim is weaker than it looks.

25. Container rules

A .afds package is a ZIP archive with a particular name, a particular pair of required entries, and a particular set of restrictions.

25.1 Normative container requirements

A conforming .afds package MUST satisfy every requirement in this table.

RequirementStatement
ZIP syntaxThe file MUST use ZIP syntax and MUST be readable by an ordinary ZIP reader.
ExtensionThe file MUST use the .afds extension.
No enclosing directoryThe archive MUST NOT wrap its contents in a single enclosing top-level directory. afds-manifest.json sits at the archive root.
Root manifestThe archive MUST include an entry named exactly afds-manifest.json at the archive root.
Root inventoryThe archive MUST include an entry named exactly afds-inventory.json at the archive root.
Normalised relative pathsEvery entry path MUST be a normalised relative path using / as the separator.
No absolute pathsEntry paths MUST NOT begin with / and MUST NOT contain a drive letter or UNC prefix.
No traversalEntry paths MUST NOT contain a .. path segment, and MUST NOT contain a . segment.
UTF-8 textText content MUST be stored as UTF-8. A producer MUST NOT emit a byte-order mark.
No encryptionThe archive MUST NOT contain encrypted entries when it is intended for portable interchange.

25.2 Notes on the container requirements

The no-enclosing-directory rule exists so that a consumer can locate the manifest without guessing. Many archive tools add a wrapper directory by default, so a producer MUST check its output rather than trusting the tool.

The path restrictions exist for security as well as tidiness, and clause 32 explains the attack they defend against. A consumer MUST reject a non-conforming path rather than attempting to sanitise it, because sanitising silently changes what the package says.

The encryption prohibition applies to portable interchange, which is the only case this specification specifies. A producer MAY encrypt a package for private transfer by wrapping the conforming .afds file in some other envelope, but the .afds file inside that envelope MUST itself be unencrypted.

Directory entries are permitted but carry no meaning. A consumer MUST NOT rely on the presence of an explicit directory entry, and a producer SHOULD omit them. Directory entries MUST NOT appear in the inventory, because they have no content to digest.

26. Media type

The underlying registered media type is application/zip.

AFDS has no dedicated IANA media-type registration. Until it has one, application/zip is the correct type to serve a .afds file with, and the .afds extension together with the root afds-manifest.json entry identify the format.

A consumer MUST NOT rely on a media type of application/afds+zip or similar being present, because no such type is registered. A consumer SHOULD identify a package by opening it and finding a parseable root manifest whose afdsFormat field is afds-package. A producer MAY additionally advertise application/afds+zip in a private context where both ends agree, but MUST NOT treat that as a registered type.

Obtaining an IANA registration is recorded as an open question in the project open-questions register.

27. Declared package hierarchy

A package declares a fixed hierarchy so that a consumer knows where each kind of artefact lives without consulting a directory listing.

27.1 The hierarchy described

At the archive root sit exactly two required files. afds-manifest.json states what the package is and where its canonical sources are. afds-inventory.json states what the package contains, byte for byte.

Beneath the root sit up to nine directories. tokens/ holds design-token files. components/ holds one subdirectory per component, each containing a machine-readable contract and a human-readable specification. patterns/ holds multi-component flow documentation. manifests/ holds generated interface manifests such as a Custom Elements Manifest. evidence/ holds assistive-technology evidence records and known-limitations prose. adapters/ holds one subdirectory per adapter target, each with a declaration, a transform report, and, for an export adapter, the generated output. docs/ holds package documentation. schemas/ holds JSON Schema documents for the package's own machine-readable artefacts. stories/ holds executable examples and fixtures.

A licence summary, LICENSES.md, MAY sit at the root. No other root-level file is defined by this specification, and a producer SHOULD NOT add one.

27.2 The hierarchy as a table

PathKindRequiredContents
afds-manifest.jsonFileREQUIREDPackage identity, version, licences, profile, and canonical source declarations
afds-inventory.jsonFileREQUIREDOne record per package entry except itself, with length, media type, role, and digest
tokens/DirectoryREQUIRED in every profileDesign-token files validating against the declared Design Tokens Format Module version
components/DirectoryREQUIRED in the components and full profilesOne subdirectory per component
patterns/DirectoryOPTIONALMulti-component flow and guidance documentation
manifests/DirectoryOPTIONALGenerated interface manifests, for example a Custom Elements Manifest
evidence/DirectoryREQUIRED in the full profileEngine-qualified evidence records and known-limitations prose
adapters/DirectoryOPTIONALAdapter declarations, transform reports, and export output
docs/DirectoryRECOMMENDEDHuman-readable package documentation
schemas/DirectoryOPTIONALJSON Schema documents for the package's machine-readable artefacts
stories/DirectoryOPTIONALExecutable examples and test fixtures
LICENSES.mdFileRECOMMENDEDHuman-readable statement of the licensing arrangement

27.3 Rules about the hierarchy

A producer MUST NOT place a canonical token file outside tokens/. A producer MUST NOT place a component contract outside components/. A producer MUST NOT place adapter output or a transform report outside adapters/.

An empty optional directory carries no information. A producer SHOULD omit an optional directory rather than shipping it empty, and MUST declare the absence in the manifest where the manifest has a corresponding field. An empty array in the manifest is a positive declaration of absence and is preferable to omitting the field.

28. Artefact roles

Every inventoried entry has exactly one role. The role records who owns the fact the entry carries, which is the mechanism that keeps the accessibility contract portable.

28.1 The six roles

RoleMeaning
canonicalThe authoritative source of the facts it carries. Nothing else in the package may contradict it.
derivedGenerated from one or more canonical artefacts and reproducible from them.
adapterProduced by an adapter for a specific external target, and shaped by that target's limits.
evidenceA record of observation: what was tested, on which engine and assistive technology, on what date, with what result.
documentationHuman-readable prose explaining canonical artefacts. Explanatory, not authoritative.
schemaA machine-readable schema that other artefacts in the package validate against.

28.2 The ownership rule

A derived or adapter artefact MUST NOT be the only source of a fact owned by a canonical artefact.

The rule follows from what each role means. A token value is owned by the canonical token file. A component's semantic model, derivation, keyboard contract, Reflow behaviour, WCAG mapping, guarantees, non-guarantees, assertions, and uncertainty are owned by the canonical component contract. An observation of assistive-technology behaviour is owned by an evidence record. A guarantee's substantiation status is owned by neither, because it is computed from the two together and MUST NOT be written into either, as clause 14.3 requires.

If a fact exists only in a generated stylesheet, a design-tool library, or a platform resource bundle, then the fact has left the portable bundle. At that point the package no longer carries the accessibility contract, which is the exact failure the format exists to prevent.

Two testable consequences follow.

The first is that any derived or adapter artefact MUST be regenerable from the canonical artefacts in the same package alone. If regeneration loses a fact, the fact was only in the derived artefact and the package does not conform. clause 33.4 states the single exception, which is an import report, because an import reads a source that lies outside the package by definition.

The second is that a consumer MAY discard every derived and adapter entry and still hold a complete design system. A verifier can approximate this check by confirming that no canonical artefact references a derived or adapter path as its source.

28.3 Documentation is not authoritative

A documentation artefact explains a canonical artefact and MUST NOT introduce a normative fact of its own. Where prose and contract disagree, the contract wins and the prose is a defect to be corrected. This is stated because a reader naturally trusts the readable file over the machine-readable one, and in this format that instinct is wrong.

29. afds-manifest.json

The manifest states what the package is, who may use it and under what terms, which profile it claims, and where every canonical source lives.

29.1 Field specification

Nesting is shown with dotted paths. A field marked REQUIRED MUST be present; a field marked OPTIONAL MAY be omitted, and a consumer MUST NOT infer a default beyond the one stated.

FieldTypeRequiredMeaning
afdsFormatStringREQUIREDFormat identifier. MUST be the exact string afds-package.
afdsVersionStringREQUIREDVersion of this package format, as semantic versioning. 1.0.0 for this specification.
packageIdStringREQUIREDStable identifier for the package, unique within its publisher's namespace. Reverse-DNS form is RECOMMENDED.
packageVersionStringREQUIREDSemantic version of the package payload, independent of afdsVersion.
titleStringREQUIREDHuman-readable package title.
descriptionStringREQUIREDProse description of what the package contains and is for.
createdStringREQUIREDCreation date of this package version, as an ISO 8601 date.
conformanceProfileStringREQUIREDDeclared completeness profile identifier from clause 34. The field name is retained from the first release of the format; the value it carries is a completeness profile and nothing else.
methodProfilesArray of stringsREQUIRED, MAY be emptyMethod profile identifiers the package claims, per clause 20.2. An empty array declares that no method is claimed. Every element not listed in clause 20.4 MUST have a matching entry in localProfiles.
targetConformanceLevelStringREQUIREDThe package's default target WCAG level, per clause 12.4. One of A, AA, AAA. Not inferable from any other field.
licences.codeStringREQUIREDSPDX identifier for code and machine-readable artefacts.
licences.documentationStringREQUIREDSPDX identifier for prose.
publisher.nameStringREQUIREDName of the person or organisation publishing the package.
publisher.projectStringOPTIONALProject the package belongs to.
publisher.uriStringOPTIONALPublisher URI. Informational only; it proves nothing about provenance.
tokens.dtcgVersionStringREQUIREDVersion of the Design Tokens Format Module that the token files validate against.
tokens.canonicalSourcesArray of source objectsREQUIREDCanonical token files. MUST contain at least one entry in every profile.
components.canonicalSourcesArray of component objectsREQUIRED in the components and full profilesCanonical component declarations.
patterns.canonicalSourcesArray of source objectsOPTIONALCanonical pattern documentation, and the pattern registry where clause 29.4 requires one. An empty array declares absence.
localProfilesArray of local profile objectsOPTIONALMethod profiles defined by this package rather than by Part III, per clause 29.3.
evidence.canonicalSourcesArray of source objectsREQUIRED in the full profileCanonical evidence records.
schemas.canonicalSourcesArray of source objectsOPTIONALSchema documents shipped in the package.
documentation.sourcesArray of source objectsOPTIONALDocumentation artefacts worth enumerating.
adaptersArray of adapter objectsREQUIREDDeclared adapters. An empty array declares that the package ships none.
storiesArray of source objectsOPTIONALExecutable examples and fixtures.
notesArray of stringsOPTIONALStatements a consumer should read before relying on the package.

A source object has the following fields.

FieldTypeRequiredMeaning
idStringREQUIREDIdentifier unique within its array.
pathStringREQUIREDPackage-relative path to the artefact. MUST appear in the inventory.
roleStringREQUIREDOne of the six roles in clause 28.
descriptionStringRECOMMENDEDWhat the artefact carries.

A component object replaces path with two paths, because a component always has both a contract and a prose specification.

FieldTypeRequiredMeaning
idStringREQUIREDStable component identifier.
nameStringREQUIREDHuman-readable component name.
kindStringREQUIREDComponent kind, for example layout-primitive or interactive-component.
specificationStringREQUIREDPath to the machine-readable contract.
documentationStringREQUIREDPath to the human-readable specification.
roleStringREQUIREDMUST be canonical.

An adapter object is specified in clause 33.

29.2 Worked example

The example below is the complete manifest of the sample package that accompanies this specification, with the adapters array shown empty because that package ships no adapters. Read it alongside the field table: every REQUIRED field appears, and every optional array that is absent from the payload is present as an empty array rather than omitted.


{
  "afdsFormat": "afds-package",
  "afdsVersion": "1.0.0",
  "packageId": "com.a11ybob.abd.afds-sample",
  "packageVersion": "1.0.0",
  "title": "AFDS Sample",
  "description": "A minimal but complete Accessibility Focused Design System package.",
  "created": "2026-08-29",
  "conformanceProfile": "afds-components",
  "methodProfiles": ["afds-patterns-native-first"],
  "targetConformanceLevel": "AA",
  "licences": {
    "code": "GPL-3.0-only",
    "documentation": "CC-BY-SA-4.0"
  },
  "publisher": {
    "name": "Bob Dodd",
    "project": "Accessible by Design",
    "uri": "https://a11ybob.com/"
  },
  "tokens": {
    "dtcgVersion": "2025.10",
    "canonicalSources": [
      {
        "id": "core",
        "path": "tokens/core.tokens.json",
        "role": "canonical",
        "description": "Core spacing, typography, measure, and colour tokens."
      }
    ]
  },
  "components": {
    "canonicalSources": [
      {
        "id": "stack",
        "name": "Stack",
        "kind": "layout-primitive",
        "specification": "components/stack/stack.spec.json",
        "documentation": "components/stack/stack.md",
        "role": "canonical"
      }
    ]
  },
  "patterns": {
    "canonicalSources": [
      {
        "id": "pattern-registry",
        "path": "patterns/registry.json",
        "role": "canonical",
        "description": "Package-level pattern registry required of a package claiming afds-patterns-native-first, at specification clause 24.2."
      }
    ]
  },
  "evidence": {
    "canonicalSources": [
      {
        "id": "at-matrix",
        "path": "evidence/at-matrix.json",
        "role": "evidence",
        "description": "Engine-qualified evidence records. All results are placeholders."
      }
    ]
  },
  "schemas": { "canonicalSources": [] },
  "adapters": [],
  "stories": [],
  "notes": [
    "AFDS 1.0.0 is a project draft, not a W3C standard.",
    "Inventory integrity is not a digital signature and does not prove provenance."
  ]
}

Three details in the example are worth naming. The dtcgVersion field is what makes token validation possible at all, because a validator otherwise has to guess which version of the token format applies. The notes array carries the two statements a consumer most needs before trusting the package. The empty adapters array is a positive declaration, not an oversight, and clause 27.3 requires it in preference to omitting the field.

29.3 Local profile declarations

Clause 20.4 permits an organisation to define its own method profile, and requires its identifier to carry a prefix that is not afds-. This clause specifies how such a profile travels in a package.

A package that lists an identifier in methodProfiles which is not defined in clause 20.4 MUST declare that profile in a localProfiles array. A localProfiles entry MUST NOT use an identifier defined in clause 20.4, because those profiles are defined by this specification and a package cannot redefine them. A package MUST NOT supply a provenance object for a profile defined in clause 20.4, for the same reason.

A local profile object has the following fields.

FieldTypeRequiredMeaning
idStringREQUIREDProfile identifier. MUST NOT begin with afds-.
titleStringREQUIREDHuman-readable profile name.
statementStringREQUIREDThe profile's statement, as clause 20.1 requires of every profile.
specificationStringOPTIONALPackage-relative path to the profile's full text. MUST appear in the inventory where present.
provenanceProvenance objectREQUIREDThe profile's provenance, per clause 20.6 and clause 29.3.1.

29.3.1 The serialized provenance object

Clause 20.6 defines the provenance object and its four members. This subclause fixes their serialized form.

A provenance object has the following members.

MemberTypeRequiredContent
adoptedArray of adopted entriesREQUIRED, MAY be emptyWhat the profile takes from work outside the package
changedArray of changed entriesREQUIRED, MAY be emptyWhat the profile alters about an adopted idea
originatesArray of originates entriesREQUIRED, MUST NOT be emptyWhat the profile asserts on its own authority
statementStringOPTIONALProse accompanying the structured members

An adopted entry has the following fields.

FieldTypeRequiredMeaning
idStringREQUIREDIdentifier unique within the adopted array, so that a changed entry can reference it.
whatStringREQUIREDWhat is adopted.
source.authorStringREQUIREDAuthor or publishing body of the source.
source.titleStringREQUIREDTitle of the source.
source.uriStringREQUIRED where one existsLocator for the source.

A changed entry has the following fields.

FieldTypeRequiredMeaning
adoptedRefStringREQUIREDThe id of the adopted entry this changes. MUST match an entry in the same adopted array.
whatStringREQUIREDWhat is changed.
directionStringREQUIREDOne of stricter, weaker, or different.

An originates entry has the following fields.

FieldTypeRequiredMeaning
whatStringREQUIREDWhat originates in the profile.
appliesToStringREQUIREDThe clause or requirement it applies to.

Four checks are mechanical, and a verifier MAY perform them.

Every adoptedRef resolves to an id in the same adopted array. Every direction is one of the three permitted values. originates is not empty, which clause 20.6 requires and which follows from clause 20.1. Every adopted entry carries an author and a title.

No check establishes that an attribution is truthful, and a tool MUST NOT report a passing structural check as a verified provenance. Clause 20.5 makes attributing a requirement to a source that does not support it a conformance failure, and detecting that failure requires reading the source.

29.4 The pattern registry

Clause 24.2 requires a package claiming afds-patterns-native-first to carry a package-level registry of component and pattern statuses. This clause binds it to a path.

A package claiming afds-patterns-native-first MUST carry the registry at patterns/registry.json. The registry MUST be declared in patterns.canonicalSources with role canonical, and MUST appear in the inventory.

The path is reserved. A package MUST NOT use patterns/registry.json for anything other than a registry satisfying clause 24.2, whether or not it claims the profile.

The registry is canonical rather than derived, even though its component entries restate a fact each component contract already carries. The reason is its prohibition entries, which record a pattern the package has declined and which no component contract can supply, because a decision not to build something leaves no component behind to declare it. Clause 24.2 requires the registry not to disagree with any component's own declaration and makes the component governing where they differ, so a conforming package contains no contradiction for the canonical role to arbitrate.

30. afds-inventory.json

The inventory is what makes a package verifiable. It lists every entry with enough information to detect any change between production and consumption.

30.1 What the inventory covers

The inventory MUST contain exactly one record for every entry in the archive, with one exception: it MUST NOT contain a record for itself.

The exclusion is necessary rather than stylistic. A record of the inventory inside the inventory could never hold a correct digest, because writing the digest would change the bytes it describes. Directory entries are also excluded, as clause 25.2 states, because they have no content.

A consumer MUST verify the inventory before relying on any package content. This means before parsing a token file, before reading a component contract, and before extracting anything to disk.

30.2 Field specification

FieldTypeRequiredMeaning
afdsFormatStringREQUIREDMUST be the exact string afds-inventory.
afdsVersionStringREQUIREDPackage-format version, matching the manifest.
packageIdStringREQUIREDMUST match the manifest's packageId.
packageVersionStringREQUIREDMUST match the manifest's packageVersion.
digestAlgorithmStringREQUIREDMUST be the exact string SHA-256.
digestEncodingStringREQUIREDMUST be the exact string lowercase-hex.
excludesSelfBooleanREQUIREDMUST be true, stating explicitly that the inventory omits itself.
entryCountNumberREQUIREDNumber of records. MUST equal the length of records.
descriptionStringRECOMMENDEDProse statement of what the inventory does and does not prove.
recordsArray of record objectsREQUIREDOne record per inventoried entry.

Each record object has five REQUIRED fields.

FieldTypeRequiredMeaning
pathStringREQUIREDPackage-relative normalised path of the entry.
mediaTypeStringREQUIREDMedia type of the entry's content, including a charset parameter for text.
byteLengthNumberREQUIREDExact uncompressed length of the entry in bytes.
roleStringREQUIREDOne of the six roles in clause 28.
sha256StringREQUIREDSHA-256 digest of the entry's exact uncompressed bytes, as lowercase hexadecimal.

Records SHOULD be sorted by path in ascending byte order. Sorting is a review convenience: a rebuilt inventory then produces a diff that shows only genuine changes.

30.3 Worked example

The example below is an abridged inventory from the sample package. Two of the nine records are shown; the omitted records have the same shape.


{
  "afdsFormat": "afds-inventory",
  "afdsVersion": "1.0.0",
  "packageId": "com.a11ybob.abd.afds-sample",
  "packageVersion": "1.0.0",
  "digestAlgorithm": "SHA-256",
  "digestEncoding": "lowercase-hex",
  "excludesSelf": true,
  "entryCount": 9,
  "description": "Inventory of every entry except this inventory itself. These digests detect transfer changes; they are not a digital signature.",
  "records": [
    {
      "path": "afds-manifest.json",
      "mediaType": "application/json",
      "byteLength": 2767,
      "role": "canonical",
      "sha256": "b480866e44ae0d66..."
    },
    {
      "path": "tokens/core.tokens.json",
      "mediaType": "application/json",
      "byteLength": 3055,
      "role": "canonical",
      "sha256": "b45bb732e28f4c3f..."
    }
  ]
}

The digests in the example are truncated for readability. In a real inventory a sha256 value MUST be the full 64 lowercase hexadecimal characters, and a consumer MUST reject a truncated, uppercase, or base-64 digest rather than attempting to interpret it.

31. Verification algorithm

A conforming consumer implements the following procedure. The steps are ordered so that a cheap check never runs after an expensive one it could have prevented, and so that nothing is parsed before the container is known to be safe.

  1. Open as ZIP. Open the file using ZIP syntax. If it is not a readable ZIP archive, report a container failure and stop.
  2. Check paths. For every entry, confirm the path is a normalised relative path, contains no .. or . segment, does not begin with /, and carries no drive letter or UNC prefix. Confirm no single enclosing top-level directory wraps the contents. Report each violation and stop, and do not sanitise.
  3. Check encryption and limits. Confirm no entry is encrypted. Apply the configured limits from clause 32 for entry count, total compressed size, total uncompressed size, per-entry decompression ratio, nesting depth, and path length. Report each violation and stop.
  4. Locate and parse the manifest. Confirm afds-manifest.json exists at the archive root, decode it as UTF-8, parse it as JSON, and confirm afdsFormat is afds-package. Read afdsVersion and apply the version rules in clause 35.
  5. Locate and parse the inventory. Confirm afds-inventory.json exists at the archive root, decode it as UTF-8, and parse it as JSON. Confirm afdsFormat is afds-inventory, digestAlgorithm is SHA-256, digestEncoding is lowercase-hex, and excludesSelf is true. Confirm packageId and packageVersion match the manifest.
  6. Confirm completeness in both directions. Confirm that every archive entry other than the inventory itself and other than directory entries has exactly one inventory record, and that every inventory record names an entry that exists. Confirm the inventory holds no record for itself. Confirm entryCount equals the number of records. Report every unmatched name in both directions.
  7. Compare byte lengths. For each record, compare the entry's uncompressed length with byteLength. Report every mismatch.
  8. Recompute and compare digests. For each record, compute the SHA-256 digest of the entry's exact uncompressed bytes and compare it with sha256 as lowercase hexadecimal. Report every mismatch. If any digest fails, the consumer MUST NOT rely on any package content.
  9. Validate token files. For each canonical token source named in the manifest, decode it as UTF-8, parse it as JSON, and validate it against the Design Tokens Format Module version declared in tokens.dtcgVersion. Report every validation failure. A consumer that cannot validate against the declared version MUST report that it did not validate, rather than passing the step silently.
  10. Report. Emit a single report giving a pass or fail verdict, the count of entries checked, and every individual problem found. A consumer MUST NOT report a pass when any step failed, and MUST distinguish "checked and passed" from "not checked".

Two properties of the procedure are deliberate.

Steps 2 and 3 run before anything is parsed or extracted, so a hostile archive is rejected before its content is touched. Steps 6 to 9 gather all problems rather than stopping at the first, because a partial report causes a producer to fix one defect at a time.

32. Security requirements

A package arrives from somewhere else, so a consumer must treat it as untrusted input.

32.1 Path traversal

A ZIP archive stores a path for each entry, and nothing in ZIP syntax prevents that path being absolute or containing .. segments. A naive extractor that joins the entry path onto an output directory can therefore be made to write outside that directory, overwriting arbitrary files.

A consumer MUST reject any entry whose path is absolute, contains a .. or . segment, or is not normalised. A consumer MUST perform this check before extracting anything. A consumer MUST NOT rewrite an offending path into a safe one, because that silently changes what the package says and hides the attack.

32.2 Decompression limits

A small archive can expand to an enormous volume of data, exhausting memory or disk. This is the zip-bomb class of attack, and nesting archives inside archives multiplies it.

A consumer MUST enforce configured limits and MUST fail rather than continuing when a limit is reached.

LimitPurposeSuggested default
Entry countBound the number of records and file handles5000 entries
Total compressed sizeBound the input read32 MiB
Total uncompressed sizeBound memory and disk consumption256 MiB
Per-entry decompression ratioDetect a single highly compressible entry200 to 1
Nesting depthBound path recursion and nested archives16 path segments
Path lengthBound filesystem interaction255 characters

The defaults above are suggestions, not requirements. A consumer MUST make its limits configurable and MUST report which limit was exceeded, so that a legitimately large package can be handled by raising a named limit rather than by disabling the checks.

A consumer SHOULD compute the uncompressed total from the archive's own metadata first and reject an over-large package before decompressing anything, then enforce the same limit again during decompression, because the declared metadata may lie.

32.3 Integrity is not authenticity

Inventory integrity is not a digital signature.

SHA-256 digests detect that content changed between the moment the inventory was written and the moment it was verified. That is genuinely useful: it catches truncated downloads, corrupted media, accidental edits, and careless repackaging.

It does not do any of the following, and a consumer MUST NOT claim otherwise.

PropertyProvided by the inventory?
Detects accidental or in-transit changeYes
Detects a change made after the inventory was writtenYes
Identifies who produced the packageNo
Proves the package came from the claimed publisherNo
Prevents an attacker rewriting content and rebuilding the inventoryNo
Establishes a chain of custodyNo

The reason is simply that an attacker who can alter the content can also recompute the digests and rewrite the inventory. Nothing in the package binds it to a key, so nothing in it can be attributed. The publisher object in the manifest is a claim, not evidence.

A future signature mechanism is therefore needed for trusted distribution, and the project open-questions register records it as open. Until such a mechanism exists, trust in a package MUST come from the channel it arrived on rather than from the package itself.

33. Adapters

An adapter moves information between the canonical artefacts of a package and the representation an external tool or platform uses. Figma, Penpot, CSS custom properties, native platform resources, and Electron shells are all adapter targets.

An adapter has a direction. An export adapter reads canonical artefacts and writes the representation a target expects. An import adapter reads a target's representation and drafts the artefacts an AFDS package requires.

Both directions are in scope. The reason is recorded in the project colophon: a format that can only export can be adopted only by a design system that began in it, and no established design system did.

The two directions do not carry the same obligations. An export knows the full set of facts it is permitted to state, because it reads artefacts that own them. An import does not, because the representation it reads was never obliged to carry an accessibility contract at all.

33.1 Direction

Each element of the manifest's adapters array MUST declare exactly one direction, either export or import. A target supported in both directions MUST be declared as two adapters sharing a target value.

One direction per declaration is required because the two produce different artefacts and different reports. A single object describing both would leave a consumer unable to determine which obligations had been discharged.

33.2 Requirements common to both directions

An adapter MUST report its mappings and its warnings, and MUST report whatever it could not carry. An adapter MUST NOT silently flatten meaning.

Silent flattening is the more dangerous behaviour of the two, because the output looks complete. A ch-based measure has no direct native analogue. A forced-colours boundary has no equivalent in a target that has no concept of a user-forced colour palette. A keyboard contract has no representation at all in a token pipeline. In each case the honest output is a recorded finding, not an approximation presented as an equivalent.

No adapter in either direction may produce an artefact with the role canonical. clause 28.2 gives the reason: an artefact shaped by a target's limits cannot own a fact.

33.3 Export adapters

Export output MUST carry the role adapter or derived, never canonical. Export output MUST be regenerable from the canonical artefacts alone, as clause 28.2 requires. A producer MUST place export output under adapters/<target>/out/.

33.4 Import adapters

An import adapter MUST NOT write an artefact with the role canonical.

The output of an import is a draft. A draft becomes canonical only when a person reviews it, supplies what the source could not, and accepts responsibility for the accessibility claims the artefact then makes. This specification calls that act promotion. Promotion MUST be performed by a person and MUST NOT be performed by a transform, because a canonical artefact asserts a contract that somebody has to be willing to defend.

Import output is therefore not itself a package artefact. A producer MUST NOT ship an unpromoted draft in a conforming package. What the package retains from an import is the import report, which is the provenance of every artefact promoted from that import.

An import report MUST carry the role adapter, and is exempt from the regenerability consequence stated in clause 28.2. The exemption is narrow and its reason is structural: an import reads a source that lies outside the package by definition, so no package can regenerate it. The alternative to the exemption is to discard the provenance of every imported artefact, which is a worse outcome than a stated exception.

Every gaps entry in an import report MUST appear in the promoted artefact as an uncertainty record or as a declared non-guarantee. An import that could not supply a fact does not thereby excuse the package from declaring that the fact is unknown.

An import MUST be a discrete run that produces a dated report. An import MUST NOT be a live read-through dependency on an external tool's model. A read-through dependency makes the external tool the effective owner of whatever it supplies, which is the failure clause 28.2 exists to prevent, and it leaves no report a reviewer can examine.

33.5 The adapter declaration

Each element of the manifest's adapters array is an adapter object.

FieldTypeRequiredMeaning
idStringREQUIREDAdapter identifier, unique within the package.
directionStringREQUIREDEither export or import.
targetStringREQUIREDThe external tool or platform, for example figma or css-custom-properties.
adapterVersionStringREQUIREDSemantic version of the adapter that produced the output.
declarationStringREQUIREDPath to the adapter's own declaration file.
reportStringREQUIREDPath to the transform report.
inputsArray of stringsREQUIREDFor export, paths of the canonical artefacts consumed. For import, identifiers of the external sources read, which are not package paths.
outputsArray of stringsREQUIREDFor export, paths of the generated artefacts. For import, an empty array, because import output is not a package artefact.
promotedArray of stringsREQUIRED for importPaths of the canonical artefacts promoted from this import. An empty array where nothing has yet been promoted.

33.6 The transform report

A transform report records what the adapter did, what it could not do, and what it wants a reader to notice.

The following fields are REQUIRED in both directions.

FieldTypeMeaning
adapterIdStringIdentifier of the adapter that produced this report.
adapterVersionStringVersion of the adapter.
directionStringEither export or import, matching the adapter declaration.
targetStringThe external tool or platform.
runDateStringISO 8601 date of the transform run.
validationStatusStringOne of passed, passed-with-warnings, or failed.
mappingsArray of mapping objectsOne record per fact carried across.
warningsArray of finding objectsFacts carried across with a caveat. Empty array if none.

An export report additionally REQUIRES the following two arrays.

FieldTypeMeaning
lossesArray of finding objectsFacts the target could not accept. Empty array if none.
unsupportedArray of finding objectsSource features the target has no concept of. Empty array if none.

An import report additionally REQUIRES the following two arrays.

FieldTypeMeaning
gapsArray of finding objectsFacts that an AFDS artefact requires and the source could not supply. Empty array if none.
unmappedArray of finding objectsSource content for which AFDS has no representation. Empty array if none.

A mapping object has source, sourceKind, targetName, and fidelity, where fidelity is one of exact, approximate, or partial. A finding object has source, severity, statement, and consumerAction, where severity is one of info, warning, or error and consumerAction says plainly what a person consuming the output must do about it.

Every array is REQUIRED even when empty. An empty losses array is a positive claim that nothing was lost, which a reviewer can challenge; an omitted losses field is merely silence. The same reasoning applies to gaps: an empty gaps array claims that the source supplied every fact an AFDS artefact requires, which is a strong claim and rarely a true one.

An export report containing a losses or unsupported entry with severity error MUST set validationStatus to failed.

An import report containing a gaps entry with severity error MUST set validationStatus to failed. A failed import report is not a malfunction, and for most targets it is the expected result. It states that the source cannot yield a conforming artefact without human authorship, which is information a person needs before deciding how much work an adoption will cost.

34. Completeness profiles

A completeness profile lets a package say how complete it is, so that a consumer can reject a package that lacks what the consumer needs without inspecting the whole hierarchy.

The manifest's conformanceProfile field carries exactly one profile identifier.

This field states completeness only. It is a different axis from the methodProfiles array, which states which of the specification's method profiles the package claims, and from targetConformanceLevel, which states the WCAG level the package targets by default. None of the three may be inferred from either of the others: a package may be afds-full, claim no method, and target Level A.

ProfileIdentifierRequires
Tokens onlyafds-tokensRoot manifest and inventory, and at least one canonical token file declared in tokens.canonicalSources
Componentsafds-componentsEverything in afds-tokens, plus at least one component with both a machine-readable contract and a human-readable specification
Fullafds-fullEverything in afds-components, plus canonical evidence records and a known-limitations artefact, and a declared test fixture for every component

Three rules govern profiles.

A package MUST satisfy every requirement of the profile it declares. A package MAY exceed its declared profile, so a consumer MUST treat the profile as a floor rather than a description. A consumer that requires a higher profile than the package declares MUST refuse to treat the package as sufficient, even if inspection suggests the extra artefacts are present, because an undeclared artefact carries no commitment to remain present in the next version.

The afds-full profile requires evidence records but does not require that they contain results. A record whose result is not-yet-tested conforms. This is deliberate: recording an untested combination is the mechanism by which uncertainty becomes visible, and a profile that demanded results would create pressure to invent them.

35. Versioning

Two versions travel in every package and they move independently.

afdsVersion is the version of this package format. packageVersion is the version of the design-system payload. Both use semantic versioning: major for incompatible change, minor for backwards-compatible addition, patch for a correction that changes no meaning.

35.1 Format-version rules for producers

A change that adds an OPTIONAL field, an OPTIONAL directory, or a new profile is a minor change. A change that adds a REQUIRED field, removes a field, changes a field's type, or changes the meaning of an existing field is a major change. A change that corrects prose without altering a requirement is a patch change.

35.2 Consumer behaviour on an unexpected format version

SituationRequired consumer behaviour
afdsVersion major matches, minor is knownProcess normally.
afdsVersion major matches, minor is higher than the consumer knowsThe consumer MUST process the package, MUST ignore fields it does not recognise, and SHOULD report that it read a newer minor version.
afdsVersion major matches, minor is lower than the consumer knowsThe consumer MUST process the package and MUST NOT require a field introduced in a later minor version.
afdsVersion major is higher than the consumer supportsThe consumer MUST refuse to process the package and MUST report the unsupported version. It MUST NOT attempt a partial read.
afdsVersion major is lower than the consumer supportsThe consumer MAY refuse, or MAY process the package in a documented compatibility mode. It MUST state which it did.
afdsVersion is absent or unparseableThe consumer MUST treat the package as non-conforming.

The asymmetry between higher and lower majors is deliberate. A higher major may rely on semantics the consumer cannot know about, so guessing risks a silent misreading of an accessibility contract. A lower major is fully knowable, so a compatibility mode is safe as long as it is declared.

35.3 Payload-version rules

packageVersion changes when the design system changes. Removing a component, removing a token, renaming an identifier, or narrowing a guarantee is a major payload change. Adding a component, adding a token, or adding evidence is a minor payload change. Correcting prose or a typographic error is a patch payload change.

Withdrawing an assistive-technology guarantee is a major payload change even when nothing else moves, because a consumer may have relied on it. Adding an evidence record that changes a claim from uncertainty to a guarantee is a minor change, because nothing that was relied upon has been taken away.