143 lines
20 KiB
Markdown
143 lines
20 KiB
Markdown
# `@suaveplan/tpm-struct` — TPM 2.0 attestation-structure parser
|
||
|
||
**Package**: `@suaveplan/tpm-struct` (NEW)
|
||
**Status**: PROPOSED
|
||
**Type**: new-package (genesis-original; greenfield — no upstream port)
|
||
**Date**: 2026-05-11
|
||
**Path**: `packages/security/tpm-struct/`
|
||
|
||
## Why
|
||
|
||
`@suaveplan/webauthn`'s `tpm` attestation-statement verifier (W3C WebAuthn L3 §8.3 — _TPM Attestation Statement Format_) is blocked on a universal-tier TPM 2.0 attestation-structure parser. The verifier MUST decode `TPMS_ATTEST` from the `attestationData` field (TCG TPM 2.0 Library Part 2 §10.12), MUST decode `TPMT_PUBLIC` from the `pubArea` field (TCG Part 2 §12.2), MUST compute the canonical TPM name digest of that public area (TCG Part 1 §16) so it can match it against `authData.attestedCredentialData.credentialPublicKey`, and MUST verify that `extraData` from the attest blob equals the `attToBeSigned` digest the relying party recomputes (WebAuthn L3 §8.3 step 8). External libraries (`tpm-js`, `node-tss`, `tss.js`) either drag in a TPM driver, target Node-only, ship CommonJS with no type information, or implement only a sliver of Part 2 §10/§11/§12. An in-house, spec-cited, zero-dep, buffer-only parser keeps `webauthn` universal-tier and gives every future TPM 2.0 attestation context (AIK enrollment per TCG _Credential Profile EK 2.0_, remote attestation, vTPM provisioning) one canonical implementation with one error registry, one OTel surface, and one set of test vectors.
|
||
|
||
The same parser is webauthn-agnostic. The public surface decodes TPM 2.0 byte strings; it neither imports nor knows about WebAuthn's `attStmt` map, `authData`, or COSE keys.
|
||
|
||
## What changes
|
||
|
||
### New package: `@suaveplan/tpm-struct`
|
||
|
||
- ADDED: `packages/security/tpm-struct/` package scaffold (`package.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md`).
|
||
- ADDED: `src/buffer/buffer.ts` — `TpmBufferReader` (`readUint8`, `readUint16Be`, `readUint32Be`, `readUint64Be: bigint`, `readBytes(n)`, `readTpm2b(): Uint8Array`, `peek(n)`, `remaining()`, `assertEnd()`) and `TpmBufferWriter` (matching write surface plus `writeTpm2b(bytes)`, `toUint8Array()`) per TCG Part 2 §10.1 octet-stream conventions; every read past `bytes.length` raises `TPM_STRUCT.TRUNCATED` carrying cursor + requested-length in `cause`.
|
||
- ADDED: `src/buffer/buffer.types.ts` — `TpmReaderOptions`, `TpmWriterOptions`, `Tpm2bReadResult`.
|
||
- ADDED: `src/alg/alg.ts` — `TPM_ALG` frozen registry mirroring TCG Part 2 §11.1 (`NULL = 0x0010`, `RSA = 0x0001`, `SHA1 = 0x0004`, `HMAC = 0x0005`, `AES = 0x0006`, `KEYEDHASH = 0x0008`, `SHA256 = 0x000b`, `SHA384 = 0x000c`, `SHA512 = 0x000d`, `XOR = 0x000a`, `RSASSA = 0x0014`, `RSAES = 0x0015`, `RSAPSS = 0x0016`, `OAEP = 0x0017`, `ECDSA = 0x0018`, `ECDH = 0x0019`, `ECDAA = 0x001a`, `SM2 = 0x001b`, `ECSCHNORR = 0x001c`, `ECMQV = 0x001d`, `KDF1_SP800_56A = 0x0020`, `KDF2 = 0x0021`, `KDF1_SP800_108 = 0x0022`, `ECC = 0x0023`, `SYMCIPHER = 0x0025`, `CTR = 0x0040`, `OFB = 0x0041`, `CBC = 0x0042`, `CFB = 0x0043`, `ECB = 0x0044`); `TPM_ECC_CURVE` frozen registry (`NIST_P256 = 0x0003`, `NIST_P384 = 0x0004`, `NIST_P521 = 0x0005`, `BN_P256 = 0x0010`, `BN_P638 = 0x0011`, `SM2_P256 = 0x0020`); `algIdToName(id)` returns the symbolic name; unknown IDs raise `TPM_STRUCT.ALG_UNSUPPORTED`. `eccCurveToName` does the same for curves; unknown raises `TPM_STRUCT.CURVE_UNSUPPORTED`.
|
||
- ADDED: `src/alg/alg.types.ts` — `TpmAlgId`, `TpmEccCurve` literal unions.
|
||
- ADDED: `src/public/public.ts` — `parseTpmtPublic(bytes): TpmtPublic` per TCG Part 2 §12.2; reads `type` (TPMI_ALG_PUBLIC), `nameAlg` (TPMI_ALG_HASH), `objectAttributes` (32-bit TPMA_OBJECT per §8.3 — exposes raw `uint32` and named-flag accessor), `authPolicy` (TPM2B_DIGEST), `parameters` (`TPMS_RSA_PARMS` per §12.2.3.5 with `symmetric` `TPMT_SYM_DEF_OBJECT`, `scheme` `TPMT_RSA_SCHEME`, `keyBits`, `exponent` (uint32, `0` ⇒ default 65537)), `parameters` (`TPMS_ECC_PARMS` per §12.2.3.6 with `symmetric`, `scheme` `TPMT_ECC_SCHEME`, `curveID` `TPMI_ECC_CURVE`, `kdf` `TPMT_KDF_SCHEME`), `unique` (`TPM2B_PUBLIC_KEY_RSA` for RSA or `TPMS_ECC_POINT` `{ x, y }` of two `TPM2B_ECC_PARAMETER`s for ECC). KEYEDHASH and SYMCIPHER branches decode their `TPMS_*_PARMS` per §12.2.3.3 / §12.2.3.4 and return the same discriminated-union shape.
|
||
- ADDED: `src/public/public.types.ts` — `TpmtPublic` discriminator on `type`; `TpmsRsaParms`, `TpmsEccParms`, `TpmsKeyedHashParms`, `TpmsSymCipherParms`, `TpmsEccPoint`, `ObjectAttributes` (typed flag struct + raw `uint32` slot).
|
||
- ADDED: `src/public/serialize.ts` — `serializeTpmtPublic(pub): Uint8Array` produces byte-identical re-serialisation of any `parseTpmtPublic` output; powers `computeTpmtPublicName`.
|
||
- ADDED: `src/attest/attest.ts` — `parseTpmsAttest(bytes): TpmsAttest` per TCG Part 2 §10.12; validates 4-byte magic `0xff544347` (`TPM_GENERATED_VALUE`, ASCII "TCG\xff" in big-endian); reads `type` (`TPMI_ST_ATTEST` — `TPM_ST_ATTEST_CERTIFY = 0x8017`, `TPM_ST_ATTEST_QUOTE = 0x8018`, `TPM_ST_ATTEST_SESSION_AUDIT = 0x8016`, `TPM_ST_ATTEST_COMMAND_AUDIT = 0x8015`, `TPM_ST_ATTEST_TIME = 0x8019`, `TPM_ST_ATTEST_CREATION = 0x801a`, `TPM_ST_ATTEST_NV = 0x8014`, `TPM_ST_ATTEST_NV_DIGEST = 0x801c`), `qualifiedSigner` (TPM2B_NAME), `extraData` (TPM2B_DATA), `clockInfo` (17 bytes: uint64 clock, uint32 resetCount, uint32 restartCount, uint8 safe), `firmwareVersion` (uint64), and dispatches `attested` to the matching `TPMU_ATTEST` member (per §10.11). For `TPM_ST_ATTEST_CERTIFY` the `attested` is `TPMS_CERTIFY_INFO` (§10.5) — `name` + `qualifiedName` both `TPM2B_NAME`.
|
||
- ADDED: `src/attest/attest.types.ts` — `TpmsAttest` discriminator on `type`; `TpmsCertifyInfo`, `TpmsQuoteInfo`, `TpmsSessionAuditInfo`, `TpmsCommandAuditInfo`, `TpmsTimeAttestInfo`, `TpmsCreationInfo`, `TpmsNvCertifyInfo`, `TpmsNvDigestCertifyInfo`, `ClockInfo`.
|
||
- ADDED: `src/attest/serialize.ts` — `serializeTpmsAttest(att): Uint8Array` produces byte-identical re-serialisation of any `parseTpmsAttest` output.
|
||
- ADDED: `src/name/name.ts` — `computeTpmtPublicName(pub, hash): Uint8Array` per TCG Part 1 §16. Output = `nameAlg (2 BE bytes) || H_nameAlg(serializeTpmtPublic(pub))`. Hash backend injected via `HashLike` (caller supplies a SHA-1/256/384/512 implementation matching `nameAlg`; the package itself imports no hash code). `nameAlg === TPM_ALG_NULL` is rejected with `TPM_STRUCT.NAME_ALG_NULL`.
|
||
- ADDED: `src/name/name.types.ts` — `HashLike` interface (`digest(bytes: Uint8Array): Uint8Array`).
|
||
- ADDED: `src/extra-data/extra-data.ts` — `extraDataMatches(actual: Uint8Array, expected: Uint8Array, equal: ConstantTimeEqual): boolean`; the comparison function is injected to keep the package zero-runtime-dep on crypto-utils.
|
||
- ADDED: `src/extra-data/extra-data.types.ts` — `ConstantTimeEqual` function signature.
|
||
- ADDED: `src/schemas/schemas.ts` — Zod boundary schemas: `TpmsAttestSchema`, `TpmtPublicSchema`, `TpmsRsaParmsSchema`, `TpmsEccParmsSchema`, `TpmsKeyedHashParmsSchema`, `TpmsSymCipherParmsSchema`, `TpmsCertifyInfoSchema`, `ClockInfoSchema`, `Tpm2bSchema`, `TpmAlgIdSchema`, `TpmEccCurveSchema`, `ObjectAttributesSchema`. Every public parser validates output through the matching schema; failure raises `TPM_STRUCT.MALFORMED` with Zod issues attached as `cause.issues`.
|
||
- ADDED: `src/errors/error-codes.ts` — `defineCodes("TPM_STRUCT", [...])` registry: `MAGIC_INVALID`, `TRUNCATED`, `ALG_UNSUPPORTED`, `CURVE_UNSUPPORTED`, `TYPE_MISMATCH`, `MALFORMED`, `NAME_ALG_NULL`, `ATTEST_TYPE_UNSUPPORTED`, `PARAMETERS_INVALID`, `OBJECT_ATTRIBUTES_RESERVED_NONZERO`. `TpmStructError extends SuaveplanError<TpmStructErrorCode>`.
|
||
- ADDED: `src/observability/metrics.ts` — histograms `tpm_struct.parse_tpms_attest_duration_ms{outcome}`, `tpm_struct.parse_tpmt_public_duration_ms{type,outcome}`, `tpm_struct.compute_name_duration_ms{name_alg}`; counters `tpm_struct.parse_total{kind,outcome}`, `tpm_struct.parse_errors_total{code}`, `tpm_struct.alg_total{alg}`. All attributes low-cardinality enums.
|
||
- ADDED: `src/diagnostic/diagnostic.ts` — `toDiagnostic(value: TpmsAttest | TpmtPublic): string` human-readable dump with `TPM_ALG` symbolic names inlined; debug-only; never invoked from production hot paths.
|
||
- ADDED: `src/__fixtures__/webauthn-tpm-vector.ts` — known-good real-world WebAuthn `tpm`-format attestation byte strings (`attestationData` + `pubArea`) plus expected parsed structures and expected name digest. Used by the WebAuthn integration scenario.
|
||
- ADDED: `src/__benchmarks__/parse-tpms-attest.bench.ts`, `parse-tpmt-public-rsa2048.bench.ts`, `parse-tpmt-public-ecc-p256.bench.ts`, `compute-name.bench.ts` — mitata suites.
|
||
- ADDED: `src/index.ts` — explicit named re-exports; no `export *` (per `openspec/AGENTS.md` §6a).
|
||
|
||
### Subpath exports
|
||
|
||
- `.` — root barrel: `parseTpmsAttest`, `parseTpmtPublic`, `computeTpmtPublicName`, `extraDataMatches`, `TPM_ALG`, `TPM_ECC_CURVE`, error codes.
|
||
- `./buffer` — `TpmBufferReader`, `TpmBufferWriter`, types.
|
||
- `./alg` — `TPM_ALG`, `TPM_ECC_CURVE`, `algIdToName`, `eccCurveToName`, literal-union types.
|
||
- `./public` — `parseTpmtPublic`, `serializeTpmtPublic`, `TpmtPublic`, parameter / point / attribute types.
|
||
- `./attest` — `parseTpmsAttest`, `serializeTpmsAttest`, `TpmsAttest`, every `TPMU_ATTEST` member type, `ClockInfo`.
|
||
- `./name` — `computeTpmtPublicName`, `HashLike`.
|
||
- `./extra-data` — `extraDataMatches`, `ConstantTimeEqual`.
|
||
- `./schemas` — Zod boundary schemas.
|
||
- `./errors` — `TPM_STRUCT_CODES`, `TpmStructError`, `TpmStructErrorCode`.
|
||
- `./diagnostic` — `toDiagnostic`.
|
||
|
||
### Dependencies
|
||
|
||
- `@suaveplan/error workspace:^` — `defineCodes`, `SuaveplanError` base.
|
||
- `@suaveplan/types workspace:^` — shared interface contracts.
|
||
- `@opentelemetry/api catalog:observability` — histograms + counters.
|
||
- `tslib catalog:build`.
|
||
- `zod catalog:forms` — option + result boundary validation.
|
||
- **No** `node:*` imports, **no** `tpm-js` / `node-tss` / `tss.js` / `@noble/*` / `@stablelib/*`. Pure TypeScript over `Uint8Array` + `DataView`.
|
||
|
||
## Out of scope
|
||
|
||
- **TPM I/O / TPM commands** — no `TPM2_*` command marshalling, no TSS device driver, no `/dev/tpmrm0` or Windows `Tbsi_*` integration, no TPM2_TOOLS shell wrapping. _Justification_: this package is buffer-only; TPM device interaction is the job of a TSS-shaped package that is not currently planned. Folding it in would require a Node-only target and pull in driver-level concerns that no current consumer needs.
|
||
- **TPM key creation / sealing / unsealing** — no `TPM2_Create`, no `TPM2_Load`, no auth-session orchestration. _Justification_: this is a parser/serialiser, not a key manager.
|
||
- **TPM session management / HMAC / parameter encryption** — no session contexts, no encryption sessions, no parameter encryption per TCG Part 1 §19. _Justification_: same — buffer parsing only.
|
||
- **AIK / EK credential validation chain** — TCG _Credential Profile_ EK 2.0 procedures (EK certificate verification, manufacturer-CA pin) are the WebAuthn verifier's job; this package returns the parsed structures it needs to do that work.
|
||
- **TPM 1.2 structures** — TPM 1.2 (`TPM_PUBKEY`, `TPM_QUOTE_INFO`) is out of scope; the WebAuthn `tpm` format mandates TPM 2.0.
|
||
- **COSE / CBOR / WebAuthn-format-specific concerns** — `@suaveplan/cbor` handles CBOR, `@suaveplan/webauthn` handles the `attStmt` map structure. This package never sees CBOR bytes.
|
||
|
||
## Source mapping
|
||
|
||
This package is genesis-original; no `../suaveplan/enterprise/tpm-struct.md` predecessor exists. The source-of-record is the TCG TPM 2.0 specification (Library Part 2 — Structures, revision 1.59, plus Library Part 1 — Architecture for the name digest) and W3C WebAuthn Level 3 for the consumer contract.
|
||
|
||
| Requirement | Source | Notes |
|
||
|---|---|---|
|
||
| `parseTpmsAttest` magic check (`0xff544347` / "TCG\xff") | TCG Part 2 §10.12 `TPMS_ATTEST.magic` + §6.2 `TPM_GENERATED_VALUE` | Big-endian uint32; mismatch ⇒ `MAGIC_INVALID` |
|
||
| `parseTpmsAttest` type field | TCG Part 2 §10.12 `type: TPMI_ST_ATTEST` + §10.5 `TPM_ST` constants | Eight-member subset of `TPM_ST_ATTEST_*` |
|
||
| `parseTpmsAttest` qualifiedSigner / extraData / clockInfo / firmwareVersion / attested | TCG Part 2 §10.12 field layout | `qualifiedSigner` = `TPM2B_NAME`; `extraData` = `TPM2B_DATA`; `clockInfo` per §10.10 (17 bytes); `firmwareVersion` = uint64 BE |
|
||
| `attested` union dispatch | TCG Part 2 §10.11 `TPMU_ATTEST` + §10.5 `TPMS_CERTIFY_INFO` | `TPM_ST_ATTEST_CERTIFY` ⇒ `TPMS_CERTIFY_INFO` (name + qualifiedName) |
|
||
| `parseTpmtPublic` type | TCG Part 2 §12.2 `TPMT_PUBLIC.type: TPMI_ALG_PUBLIC` | `RSA` / `ECC` / `KEYEDHASH` / `SYMCIPHER` |
|
||
| `parseTpmtPublic` nameAlg | TCG Part 2 §12.2 `nameAlg: TPMI_ALG_HASH` + §11.1 | `SHA-1/256/384/512`; `NULL` rejected for name-digest call site |
|
||
| `parseTpmtPublic` objectAttributes | TCG Part 2 §8.3 `TPMA_OBJECT` 32-bit bitfield | Named-flag accessor + raw uint32; reserved bits non-zero ⇒ `OBJECT_ATTRIBUTES_RESERVED_NONZERO` |
|
||
| `parseTpmtPublic` authPolicy | TCG Part 2 §12.2 `authPolicy: TPM2B_DIGEST` | Length-prefixed |
|
||
| RSA parameters | TCG Part 2 §12.2.3.5 `TPMS_RSA_PARMS` | symmetric / scheme / keyBits / exponent (uint32, `0` ⇒ default 65537 per spec) |
|
||
| ECC parameters | TCG Part 2 §12.2.3.6 `TPMS_ECC_PARMS` | symmetric / scheme / curveID (`TPMI_ECC_CURVE`) / kdf; curve lookup via §11.2.5 |
|
||
| RSA unique field | TCG Part 2 §12.2 `unique: TPM2B_PUBLIC_KEY_RSA` | Modulus bytes |
|
||
| ECC unique field | TCG Part 2 §12.2 `unique: TPMS_ECC_POINT` | `x` + `y` each `TPM2B_ECC_PARAMETER` |
|
||
| `computeTpmtPublicName` | TCG Part 1 §16 _Names_ | `nameAlg (2 BE) || H_nameAlg(TPMT_PUBLIC)` |
|
||
| `TPM2B` reader | TCG Part 2 §10.1 `TPM2B_*` size-prefixed buffer | UINT16 BE length + exact bytes |
|
||
| `TPM_ALG_ID` registry | TCG Part 2 §11.1 ALG_TABLE | 31-entry registry; unknown ⇒ `ALG_UNSUPPORTED` |
|
||
| WebAuthn consumer contract (`extraData`, `pubArea` match) | W3C WebAuthn L3 §8.3 steps 4–10 | `extraData === attToBeSigned`; `pubArea name === credentialPublicKey name` |
|
||
| EK credential context | TCG _Credential Profile EK 2.0_ v2.3 §3 | Informational — drives the `attested` union shape decisions |
|
||
|
||
### Requirements with no source line
|
||
|
||
| Requirement | Justification |
|
||
|---|---|
|
||
| `defineCodes("TPM_STRUCT", [...])` 10-code registry | Genesis convention (`CLAUDE.md` Quality + `@suaveplan/error`); each code maps to a distinct §10 / §11 / §12 / §16 failure class so callers can branch on root cause. |
|
||
| Zod boundary schemas at every public type | Genesis convention (`CLAUDE.md` §11). A buggy reader cannot leak a partially-populated object past the boundary. |
|
||
| OTel surface (parse + name histograms; parse-total + parse-errors + alg counters) | Genesis convention — every hot-path package emits histograms + counters. `alg` attribute bounded by §11.1 registry; `code` bounded by the 10-entry code registry. |
|
||
| 300-line file budget + 100/100/100/100 coverage + sibling `.md` per impl file | `CLAUDE.md` Quality + Documentation sections. |
|
||
| Property tests (round-trip + single-bit-flip) | `CLAUDE.md` "no mocks" rule; round-trip is the only test that proves parser + serialiser agree under arbitrary valid inputs. |
|
||
| Mitata benchmarks | `CLAUDE.md` global benchmark requirement; baseline numbers feed README perf table. |
|
||
| Hash-injection via `HashLike` | Cross-cutting design — keeps the package universal-tier and free of crypto-engine selection; consumer wires `@suaveplan/crypto-hash` at the call site. |
|
||
|
||
## Verification approach
|
||
|
||
Every requirement carries a `**Verified by:**` line in `specs/tpm-struct/spec.md` naming concrete `it(...)` titles inside the test files below.
|
||
|
||
- `packages/security/tpm-struct/src/buffer/buffer.test.ts` — reader cursor advancement; `readTpm2b` length/payload round-trip; `assertEnd` rejects trailing bytes; out-of-bounds reads raise `TRUNCATED`.
|
||
- `packages/security/tpm-struct/src/alg/alg.test.ts` — every §11.1 entry round-trips through `algIdToName`; unknown ID raises `ALG_UNSUPPORTED`; every §11.2.5 curve round-trips through `eccCurveToName`.
|
||
- `packages/security/tpm-struct/src/public/public.test.ts` — `parseTpmtPublic` for RSA-2048, ECC P-256, ECC P-384, KEYEDHASH, SYMCIPHER; `objectAttributes` flag round-trip; reserved bits non-zero raises `OBJECT_ATTRIBUTES_RESERVED_NONZERO`.
|
||
- `packages/security/tpm-struct/src/public/property.test.ts` — fast-check round-trip: arbitrary valid `TpmtPublic` → `serializeTpmtPublic` → `parseTpmtPublic` byte-identical.
|
||
- `packages/security/tpm-struct/src/public/serialize.test.ts` — `serializeTpmtPublic` produces byte-identical output for parsed real-world fixtures.
|
||
- `packages/security/tpm-struct/src/attest/attest.test.ts` — magic mismatch raises `MAGIC_INVALID`; every `TPM_ST_ATTEST_*` variant decodes the matching `attested` union member; unknown `type` raises `ATTEST_TYPE_UNSUPPORTED`.
|
||
- `packages/security/tpm-struct/src/attest/property.test.ts` — fast-check round-trip: arbitrary valid `TpmsAttest` → `serializeTpmsAttest` → `parseTpmsAttest` byte-identical; single-bit-flip on any field either decodes to a different value or raises a typed `TpmStructError`.
|
||
- `packages/security/tpm-struct/src/attest/serialize.test.ts` — `serializeTpmsAttest` byte-identical for parsed real-world fixtures.
|
||
- `packages/security/tpm-struct/src/name/name.test.ts` — `computeTpmtPublicName` matches the TCG Part 1 §16 expected output for SHA-1/256/384/512 nameAlg variants; `nameAlg === TPM_ALG_NULL` raises `NAME_ALG_NULL`.
|
||
- `packages/security/tpm-struct/src/extra-data/extra-data.test.ts` — `extraDataMatches` true on equal buffers, false on mismatched bytes, false on length mismatch; delegates to the injected `ConstantTimeEqual`.
|
||
- `packages/security/tpm-struct/src/schemas/schemas.test.ts` — every Zod schema accepts a known-good fixture and rejects a malformed fixture with `MALFORMED` + Zod issues on `cause`.
|
||
- `packages/security/tpm-struct/src/errors/error-codes.test.ts` — every code in the `TPM_STRUCT_CODES` registry is thrown by ≥1 production path; `expectTypeOf<TpmStructErrorCode>().toEqualTypeOf<…>()` pins the literal union.
|
||
- `packages/security/tpm-struct/src/observability/metrics.test.ts` — histograms + counters emit under a real in-memory `MeterProvider`; `alg` and `code` attributes match the bounded enums.
|
||
- `packages/security/tpm-struct/src/diagnostic/diagnostic.test.ts` — `toDiagnostic` emits symbolic algorithm names; never invokes parser hot paths (no metric emission during diagnostic).
|
||
- `packages/security/tpm-struct/src/__integration__/webauthn-vector.test.ts` — known-good WebAuthn `tpm` attestation fixture: `parseTpmsAttest(attestationData)` succeeds; `parseTpmtPublic(pubArea)` succeeds; `computeTpmtPublicName(parsedPub, sha256)` equals the expected fixture digest byte-for-byte.
|
||
- `packages/security/tpm-struct/src/index.test.ts` — every documented subpath export resolves.
|
||
|
||
## Migration impact
|
||
|
||
None — additive only; this is the first delta of a new package. No existing genesis code imports from `@suaveplan/tpm-struct`. `@suaveplan/webauthn` is the gating consumer and lands in a follow-up change once this package archives; that change SHALL add the `workspace:^` dep edge and wire the `tpm`-attestation verifier.
|
||
|
||
## Impact
|
||
|
||
### Affected dependents
|
||
|
||
`bunx turbo ls --filter=...@suaveplan/tpm-struct` returns no current dependents (the package does not yet exist on `genesis`). The pending consumer is:
|
||
|
||
- `@suaveplan/webauthn` — [ ] tests run / [ ] docs reviewed / [x] no action needed in THIS change (reason: webauthn is itself pending; the `tpm`-attestation verifier wiring lands in the webauthn migration change once this package archives, per `STATUS.md` and `openspec/AGENTS.md` §0b).
|
||
|
||
No foundation-package fan-out applies (this is a new universal-tier leaf package; foundation deps `@suaveplan/error` and `@suaveplan/types` are unchanged).
|