@plasius/schema 1.3.0 → 1.4.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.
Files changed (36) hide show
  1. package/README.md +235 -1
  2. package/THIRD_PARTY_NOTICES.md +75 -0
  3. package/dist/chunk-6FE4FLKF.js +254 -0
  4. package/dist/chunk-6FE4FLKF.js.map +1 -0
  5. package/dist/chunk-AG2NAPDC.js +117 -0
  6. package/dist/chunk-AG2NAPDC.js.map +1 -0
  7. package/dist/chunk-UQHPDQZX.js +2606 -0
  8. package/dist/chunk-UQHPDQZX.js.map +1 -0
  9. package/dist/feedback-diagnostics-BaniQAFS.d.cts +353 -0
  10. package/dist/feedback-diagnostics-CnsI0B9-.d.ts +353 -0
  11. package/dist/feedback-diagnostics-vocabulary.cjs +156 -0
  12. package/dist/feedback-diagnostics-vocabulary.cjs.map +1 -0
  13. package/dist/feedback-diagnostics-vocabulary.d.cts +70 -0
  14. package/dist/feedback-diagnostics-vocabulary.d.ts +70 -0
  15. package/dist/feedback-diagnostics-vocabulary.js +33 -0
  16. package/dist/feedback-diagnostics-vocabulary.js.map +1 -0
  17. package/dist/feedback-diagnostics.cjs +2696 -0
  18. package/dist/feedback-diagnostics.cjs.map +1 -0
  19. package/dist/feedback-diagnostics.d.cts +2 -0
  20. package/dist/feedback-diagnostics.d.ts +2 -0
  21. package/dist/feedback-diagnostics.js +39 -0
  22. package/dist/feedback-diagnostics.js.map +1 -0
  23. package/dist/feedback-unicode-profile.cjs +283 -0
  24. package/dist/feedback-unicode-profile.cjs.map +1 -0
  25. package/dist/feedback-unicode-profile.d.cts +28 -0
  26. package/dist/feedback-unicode-profile.d.ts +28 -0
  27. package/dist/feedback-unicode-profile.js +13 -0
  28. package/dist/feedback-unicode-profile.js.map +1 -0
  29. package/dist/index.cjs +2133 -14
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +879 -286
  32. package/dist/index.d.ts +879 -286
  33. package/dist/index.js +1516 -2300
  34. package/dist/index.js.map +1 -1
  35. package/package.json +51 -13
  36. package/unicode/feedback-unicode-15.1.0-unassigned.json +13 -0
package/README.md CHANGED
@@ -20,6 +20,23 @@ Apache-2.0. ESM + CJS builds. TypeScript types included.
20
20
  npm install @plasius/schema
21
21
  ```
22
22
 
23
+ ## Personal names and display names
24
+
25
+ Use `validateName` for first, middle, and last names. It accepts Unicode
26
+ letters and combining marks together with common name punctuation, but it does
27
+ not accept digits. Use `validateDisplayName` for public display names, where
28
+ Unicode decimal digits are also valid:
29
+
30
+ ```ts
31
+ import { validateDisplayName, validateName } from "@plasius/schema";
32
+
33
+ validateDisplayName("Player 2"); // true
34
+ validateName("Player 2"); // false
35
+ ```
36
+
37
+ Both validators reject empty values, control characters, unsupported symbols,
38
+ and values longer than 256 characters.
39
+
23
40
  ## Packaging policy (template baseline)
24
41
 
25
42
  `@plasius/schema` is the baseline template for new `@plasius/*` packages.
@@ -28,6 +45,46 @@ uses `dist-cjs/*.js` with `type: module`, it must generate
28
45
  `dist-cjs/package.json` with `{ "type": "commonjs" }` and validate this in
29
46
  `pack:check` before publish.
30
47
 
48
+ ### Private-artifact policy
49
+
50
+ Signed contributor agreements and contributor acceptance records belong only
51
+ in the approved access-controlled system outside source control. The
52
+ zero-dependency repository gate inspects path metadata without opening,
53
+ hashing, copying, or logging suspected private artifacts:
54
+
55
+ ```bash
56
+ npm run privacy:check
57
+ npm run test:privacy
58
+ npm run build
59
+ npm run pack:check
60
+ ```
61
+
62
+ `privacy:check` checks both filesystem metadata and the proposed Git index. It
63
+ rejects CSV files, contributor/CLA registry variants, contributor acceptance,
64
+ signature, and submission record categories, signed CLA/contributor agreement
65
+ storage, and paths that combine privacy and registry markers. Explicit public
66
+ process, template, schema, validator, and policy files remain allowed only as
67
+ terminal path components; matching private-record categories used as
68
+ directories fail closed regardless of their descendants.
69
+ Unicode compatibility normalization occurs before separator folding so
70
+ hierarchical, case-variant, and compatibility-separated protected paths cannot
71
+ evade the same path-only classification. An unstaged deletion remains a
72
+ failure until the deletion is staged. Invalid Git worktree metadata, symbolic
73
+ links, and non-regular entries fail closed, and failures report only rule IDs
74
+ and counts.
75
+
76
+ `pack:check` requires the exact `dist`, `THIRD_PARTY_NOTICES.md`, and `unicode`
77
+ `package.json.files` entries, then retains prefix-stripped raw member identities
78
+ and cardinality while comparing the canonical `npm pack --dry-run` manifest
79
+ with the package's exact public allowlist. Duplicate members, raw aliases, and
80
+ many-to-one normalization collisions fail without logging member values. The
81
+ same zero-dependency inventory policy runs over the digest-verified sealed
82
+ tarball immediately before publication. The isolated npm cache is removed
83
+ whether the check passes or fails. CI runs the repository gate before
84
+ dependency installation; release preparation and publication repeat the gate.
85
+ These path controls are defense in depth and do not replace secret scanning,
86
+ access controls, or incident response.
87
+
31
88
  ---
32
89
 
33
90
  ## Player-system event contracts
@@ -49,6 +106,158 @@ Only the stable `playerId` is treated as a personal identifier. It is retained
49
106
  for storage partitioning and pseudonymized by `sanitizeForLog`; unknown fields
50
107
  and hidden server truth are excluded by schema serialization.
51
108
 
109
+ ## Privacy-safe feedback contracts
110
+
111
+ The package exports closed, versioned contracts for feedback intake and
112
+ reporting:
113
+
114
+ ```ts
115
+ import {
116
+ FeedbackBugPacketSchema,
117
+ FeedbackPublicSummarySchema,
118
+ FeedbackRichTextAstSchema,
119
+ FeedbackReviewPacketSchema,
120
+ FeedbackTransientAnalysisRequestSchema,
121
+ validateFeedbackTransientAnalysisRequest,
122
+ } from "@plasius/schema";
123
+ import type {
124
+ FeedbackBugTransientAnalysisRequest,
125
+ FeedbackReviewTransientAnalysisRequest,
126
+ } from "@plasius/schema";
127
+ ```
128
+
129
+ Renderer and browser packages that need only the game-diagnostics vocabulary
130
+ should import `@plasius/schema/feedback-diagnostics-vocabulary`. This
131
+ lightweight entrypoint exports the canonical surface/provenance pairs, buckets,
132
+ feature/counter/error allowlists, bounds, and discriminated type without
133
+ loading the schema builder. Server validation and analysis tools should import
134
+ `@plasius/schema/feedback-diagnostics`, which adds the canonical runtime schema.
135
+ Both entrypoints derive from the same source definitions and preserve
136
+ structural parity in ESM and CommonJS builds, so consumers cannot maintain a
137
+ second diagnostics dialect. Use `validateFeedbackGameDiagnostics()` when a
138
+ consumer needs a validation result whose `value` retains the correlated
139
+ `FeedbackGameDiagnostics` TypeScript type.
140
+
141
+ Free-form feedback exists only in `FeedbackRichTextAstSchema`, which is
142
+ intended for a transient no-retention scanner. Its text nodes are marked
143
+ high-PII, clear-on-storage, and omit-from-logs. The interoperable document is
144
+ rooted at
145
+ `{ type: "doc", version: "1.0.0", schemaVersion: "1", children }`; it permits
146
+ only paragraph and bullet-list-item blocks with bounded indentation and text
147
+ nodes with bold, italic, and underline marks. Narrative must already be NFKC
148
+ normalised against the exported `unicode-15.1.0-nfkc-v1` profile before
149
+ validation. Assignment is checked against the original code units before host
150
+ NFKC is invoked. Its 4,000-character ceiling counts Unicode code points and
151
+ inter-block newlines consistently with the private scanner, with a separate
152
+ 8,000 UTF-16-unit safety ceiling. HTML, Markdown links, protocol-relative
153
+ links, and `http`, `https`, `ftp`, `mailto`, `javascript`, `data`, `blob`, and
154
+ `file` schemes are rejected.
155
+
156
+ `FeedbackTransientAnalysisRequestSchema` is closed and discriminated by
157
+ `purpose`. A bug analysis request must carry a closed `surfaceId`; the service
158
+ must verify it against the caller's capability-projected catalog before
159
+ handing ciphertext to the private scanner. A review analysis request must omit
160
+ `surfaceId`. The exported bug/review request types preserve the same
161
+ discriminator for TypeScript consumers;
162
+ `validateFeedbackTransientAnalysisRequest()` preserves that union on
163
+ successful runtime validation.
164
+
165
+ Final bug and review JSON bodies deliberately contain no `submissionId`.
166
+ Consuming HTTP APIs require `Idempotency-Key` as the sole final-submission
167
+ correlation mechanism and keep it in the isolated abuse/idempotency control
168
+ plane. It must never be copied into a draft, accepted packet, report, log, or
169
+ public/Admin/MCP projection.
170
+
171
+ The complete Unicode 15.1 unassigned corpus is shipped independently of the
172
+ host runtime:
173
+
174
+ ```ts
175
+ import {
176
+ FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256,
177
+ FEEDBACK_UNICODE_PROFILE_ID,
178
+ containsFeedbackUnicodeProfileUnsupportedText,
179
+ isFeedbackUnicodeProfileUnassigned,
180
+ } from "@plasius/schema/feedback-unicode-profile";
181
+ ```
182
+
183
+ `containsFeedbackUnicodeProfileUnsupportedText` rejects every code point
184
+ unassigned in Unicode 15.1 and every lone UTF-16 surrogate. The code-point
185
+ helper decodes canonical, delta ULEB128/base64url once and uses binary search
186
+ over 707 half-open ranges. Non-JavaScript scanners can consume the same
187
+ language-neutral corpus from
188
+ `@plasius/schema/unicode/feedback-unicode-15.1.0-unassigned.json` and verify
189
+ its published endpoint digest. The corpus is generated from the exact
190
+ build-time dependency `@unicode/unicode-15.1.0@1.6.17`; run
191
+ `npm run unicode:check` to prove the checked-in artifact is current. See
192
+ [ADR-0007](./docs/adrs/adr-0007-pinned-unicode-feedback-profile.md) and
193
+ [third-party notices](./THIRD_PARTY_NOTICES.md).
194
+
195
+ The three typed feedback subpaths publish both modern `exports.types` entries
196
+ and exact `typesVersions` fallbacks. This keeps their declarations resolvable
197
+ for consumers that still compile CommonJS with classic TypeScript
198
+ `moduleResolution: "node"`, without opening a wildcard route for unknown
199
+ subpaths.
200
+
201
+ Validated reusable fragments retain exact `type` and `version` metadata when
202
+ embedded in parent contracts. This makes the output of the encrypted-envelope,
203
+ derived-analysis, and game-diagnostics schemas directly composable into their
204
+ request or packet schemas without a metadata-stripping step.
205
+
206
+ Accepted packet, diagnostics, report, checkpoint, and public-summary schemas
207
+ cannot express narrative, reporter identity or pseudonyms, network/client
208
+ metadata, arbitrary URLs, pixels, quotes, summaries, embeddings, content
209
+ hashes, or model traces. Game surfaces, provenance contracts, features,
210
+ counters, and error codes are closed enums rather than attacker-controlled
211
+ safe-looking strings. The public summary omits counts, averages, and trends
212
+ entirely when the ten-review privacy threshold is not met and always carries
213
+ 13 canonical UTC week slots, using suppressed gaps where a week is below the
214
+ same threshold. A trend is emitted only with a preceding 90-day comparison
215
+ that independently meets the ten-review threshold; the schema derives its
216
+ delta and enforces `up`/`down` only at a change of at least 0.1 stars.
217
+
218
+ Hourly reports bind targets to the closed surface registry and include
219
+ renderer/backend/viewport/frame-rate/frame-time plus closed diagnostic
220
+ feature/counter/error distributions. Release IDs and build IDs are
221
+ server-registry values: intake clients never submit them, and consumers must
222
+ resolve them from trusted deployment metadata before packet construction.
223
+ Advisory trigger counts and ordered recommendation IDs are derived exactly
224
+ from the report distributions and processor lag rather than accepted as
225
+ free-standing claims. Feedback context caps remaining bug cooldown at 24
226
+ hours and review suppression at 30 days; acceptance receipts distinguish bugs
227
+ from reviews and accept only the five-step bug ladder or exact 30-day review
228
+ period.
229
+
230
+ All feedback contracts opt in to recursive unknown-field rejection, including
231
+ direct references and arrays of references. Unshaped references admit only
232
+ `type` and `id`; shaped references admit only those keys plus their declared
233
+ fields. Closed feedback vocabularies and definition objects are runtime frozen
234
+ before schemas capture them. Before recursive cloning, strict validation also
235
+ performs an iterative whole-value node/depth budget so malformed containers
236
+ hidden under scalar fields cannot exhaust the call stack. The
237
+ general schema default remains backwards-compatible stripping:
238
+
239
+ ```ts
240
+ const ClosedPacket = createSchema(fields, "closed-packet", {
241
+ version: "1.0.0",
242
+ piiEnforcement: "strict",
243
+ unknownFields: "reject",
244
+ identity: "exact",
245
+ });
246
+ ```
247
+
248
+ Schema-level validators receive an optional, frozen
249
+ `SchemaValidationContext` with only `wasProvided(fieldName)`. This lets a
250
+ closed discriminator distinguish omission from an explicit `null` without
251
+ giving validation hooks access to raw field values.
252
+
253
+ Unknown-field errors name only the schema-owned container and never echo an
254
+ untrusted key or value. Exact identity rejects a mismatched contract type or
255
+ version. The schemas constrain data shape; consumers must still
256
+ perform the documented private PII scan, discard narrative immediately, and
257
+ validate again before every storage write. See the
258
+ [feedback contract design](./docs/design/feedback-contracts.md) and
259
+ [ADR-0006](./docs/adrs/adr-0006-privacy-safe-feedback-contract-boundaries.md).
260
+
52
261
  ## Demo
53
262
 
54
263
  ```bash
@@ -62,7 +271,7 @@ See `demo/README.md` for the local sanity-check scaffold.
62
271
 
63
272
  ## Node.js Version
64
273
 
65
- This project uses Node.js **22** by default. The version is pinned in the [`.nvmrc`](./.nvmrc) file.
274
+ This project uses Node.js **24** by default. The version is pinned in the [`.nvmrc`](./.nvmrc) file.
66
275
 
67
276
  If you use [nvm](https://github.com/nvm-sh/nvm), simply run:
68
277
 
@@ -303,3 +512,28 @@ We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for gu
303
512
  ## License
304
513
 
305
514
  Licensed under the [Apache-2.0 License](./LICENSE).
515
+
516
+ <!-- BEGIN PLASIUS RELEASE INTEGRITY -->
517
+ ## Release integrity
518
+
519
+ CI keeps the administrative contributor registry outside Git and npm package
520
+ artifacts using exact, case-normalised path checks. Pull requests run on
521
+ GitHub-hosted runners after same-repository admission; protected `main` CI uses
522
+ the workflow-restricted self-hosted group. Release preparation and npm
523
+ publication use GitHub-hosted runners with Node.js 24.18.0 LTS.
524
+
525
+ Dispatch `cd.yml` from protected `main` with `phase: prepare`. Release metadata
526
+ lands through a unique pull request. After successful push-triggered CI for the
527
+ exact merge SHA, the workflow dispatches a separate `publish` run from that
528
+ same SHA. Publication fails closed if `main`, CI evidence, the version, tag,
529
+ prerelease identity, artifact digests, or npm registry integrity differ.
530
+
531
+ The read-only validation job installs dependencies, validates the package,
532
+ builds an SBOM, and seals an immutable tarball. The `production` job runs no
533
+ package lifecycle or dependency code; it verifies the exact artifact hand-off
534
+ and rechecks the collision-free exact raw-member inventory before publishing
535
+ that tarball through npm OIDC with provenance. No npm write token or fallback
536
+ is configured. The trusted publisher must be bound to `Plasius-LTD/schema`,
537
+ `cd.yml`, `production`, and `npm publish`. Rollback is to disable `cd.yml`;
538
+ never restore token-based publication.
539
+ <!-- END PLASIUS RELEASE INTEGRITY -->
@@ -0,0 +1,75 @@
1
+ # Third-Party Notices
2
+
3
+ The distributed projection
4
+ `unicode/feedback-unicode-15.1.0-unassigned.json` is generated from
5
+ `@unicode/unicode-15.1.0` version `1.6.17`, specifically its
6
+ `General_Category/Unassigned` ranges. The pinned package metadata identifies
7
+ Mathias Bynens as the author and declares the MIT license. Its data is derived
8
+ from Unicode data files. Both applicable notices are reproduced below.
9
+
10
+ ## `@unicode/unicode-15.1.0` — MIT License
11
+
12
+ Copyright Mathias Bynens <https://mathiasbynens.be/>
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining
15
+ a copy of this software and associated documentation files (the
16
+ "Software"), to deal in the Software without restriction, including
17
+ without limitation the rights to use, copy, modify, merge, publish,
18
+ distribute, sublicense, and/or sell copies of the Software, and to
19
+ permit persons to whom the Software is furnished to do so, subject to
20
+ the following conditions:
21
+
22
+ The above copyright notice and this permission notice shall be
23
+ included in all copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
+
33
+ ## Unicode License V3
34
+
35
+ UNICODE LICENSE V3
36
+
37
+ COPYRIGHT AND PERMISSION NOTICE
38
+
39
+ Copyright © 2016-2025 Unicode, Inc.
40
+
41
+ NOTICE TO USER: Carefully read the following legal agreement. BY
42
+ DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
43
+ SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
44
+ TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
45
+ DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
46
+
47
+ Permission is hereby granted, free of charge, to any person obtaining a
48
+ copy of data files and any associated documentation (the "Data Files") or
49
+ software and any associated documentation (the "Software") to deal in the
50
+ Data Files or Software without restriction, including without limitation
51
+ the rights to use, copy, modify, merge, publish, distribute, and/or sell
52
+ copies of the Data Files or Software, and to permit persons to whom the
53
+ Data Files or Software are furnished to do so, provided that either (a)
54
+ this copyright and permission notice appear with all copies of the Data
55
+ Files or Software, or (b) this copyright and permission notice appear in
56
+ associated Documentation.
57
+
58
+ THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
59
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
60
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
61
+ THIRD PARTY RIGHTS.
62
+
63
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
64
+ BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
65
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
66
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
67
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
68
+ FILES OR SOFTWARE.
69
+
70
+ Except as contained in this notice, the name of a copyright holder shall
71
+ not be used in advertising or otherwise to promote the sale, use or other
72
+ dealings in these Data Files or Software without prior written
73
+ authorization of the copyright holder.
74
+
75
+ SPDX-License-Identifier: Unicode-3.0
@@ -0,0 +1,254 @@
1
+ // unicode/feedback-unicode-15.1.0-unassigned.json
2
+ var feedback_unicode_15_1_0_unassigned_default = {
3
+ profileId: "unicode-15.1.0-nfkc-v1",
4
+ unicodeVersion: "15.1.0",
5
+ sourcePackage: "@unicode/unicode-15.1.0",
6
+ sourcePackageVersion: "1.6.17",
7
+ sourceProperty: "General_Category/Unassigned",
8
+ rangeEncoding: "flat-half-open-delta-uleb128-base64url-v1",
9
+ rangeCount: 707,
10
+ endpointCount: 1414,
11
+ codePointCount: 824718,
12
+ endpointSha256: "591e457524d6b4b988aa7c7687e76c95a78370fea38bbd1b09085be7fd935ef3",
13
+ payload: "-AYCBgQHAQEBFAGNAwEmAjICAwE3CBsEBguOAgE8AmUOOwIxAg8BHAIBAQsFHwECBuwBAQgCAgIWAQcBAQMEAgkCAgIECAEEAgEFAhkCAwEGBAICFgEHAQIBAgECAgEBBQQCAgMDAQcEAQEHEQoDAQkBAwEWAQcBAgEFAgoBAwEDAgEPBAIMBwcBAwEIAgICFgEHAQIBBQIJAgICAwcDBAIBBQISCgIBBgMDAQQDAgEBAQIDAgMDAwwEBQMDAQQCAQYBDhUFDQEDARcBEAIJAQMBBAcCAQMCAQIEAgoHFgEDARcBCgEFAgkBAwEEBwIGAgEEAgoBAwwNAQMBMwEDAQYEEAIaAQMBEgMYAQkBAQIHAwEEBgEBAQgGCgIDDDoEHSUCAQEBBQEYAQEBFwIFAQEBBwEKAgQgSAEkBCcBJAEPAQ0lxgEBAQUBAvkCAQQCBwEBAQQCKQEEAiEBBAIHAQEBBAIPATkBBAJDAiADGgZWAgYCnQUDWQcWCRgJFAwNAQMBAgxeAgoGCgYaBlkHKwVGCh8BDAQMBAEDKgIFCywEGgYLAz4CQQEdAgsGCgYOAh8xTQMvAXQIPAMPAzwHKwILCCsFlgQCBgImAgYCCAEBAQEBAQEfAjUBDwEOAgYBEwIDAQkBZQEMAhsBDQMhDyEPjAEElwUZCxWUDgIgAd0CBS0BAQUBAjgHAg4YCQcBBwEHAQcBBwEHAQcBBwF-IhoBWQzWARpQAVYCZwUrAV4BVAswAe3kAQM3CdwCFLgBCMsBBQIBAQEFGDsDCgY4CEYIDAZ0Cx4DTgELBCEBNwkOAgoCZxgcCgYCBgIGCQcBBwE8BH4CCgakVwwXBDEE7kQCaiYHDAUFGgEFAQEBAgECAX0QvQMCNgcBICoGMwETAQQEBQGHAQIBAb4BAwYCBgIGAgMDBwEHCgUCDAEaARMBAgEPAg4iewUDBC0DWAENAwEvLoIBHQMxDxwEJAkeBSsFHgElBA4qngECCgYkBCQEKAg0CwwBDwEHAQIBCwEPAQcBAkO3AgkWCggYBgEqAQlFBgIBASwBAgMBAhcBSAgJMBMBAgUhAxsFAUA4BBQCMgECBQgBAwEdAgMECgcJB0AgJwQMCTYDHQIbBRoHBAwHUEk3Mw0zBy4ICqYCHwEqAQMCAksrCCoWGiYcFBcJTgQkCUQKAQIZBwoGNQESCCcJYAEUCxIBLz4HAQEBBAEPAQsGOwUKBgQBCAICAhYBBwECAQUBCgICAgMCAQYBBQcCBwMFiwFcAQUeSAgKpgE2AiYiRQsKBg0TOgYKNhsCDwQXuQE8ZFMMCAIBAggBAgEeAQICDAkKRggCLgILG0gIUw1JBwr2AQkBLQEOCh0DIAIWAQ5JBwECASwDAQECAQkICgYGAQIBJQECAQYHCrYCGQcRASkDHFYBDzINmwdmbwEFC8QBzBRjDdYIqh_HBLlDuQQHHwEKBFEBCgYeAgYKRgoKAQcBFQUTsAVbZUsEOQcRQAULAg74LwjWCSoJ50UEAQcBAgGjAg8BHQMCAQ4ECIwDhBJrBQ0DCQcKAgjcJC4CFwl0PPYBCicCwgEVRnoUDBQMVwkZhwFVAUcBAgIBAgICBAEMAQEBBwFBAQQCCAEHARwBBAEFAQEDBwHUAgKkAgK-BQ8FAQ_QCB8GBtUBBwERAgcBAgEFBT4hAXAtAw4CCgQCwAIfEToFAdADKuYFBwEEAQIBDwHFAQIQKUwECgQCkQZETD3CAQQBGwECAQECAQEKAQQBAQEBBgEEAQEBAQEBAwECAQECAQEBAQEBAQEBAQIBAQIEAQcBBAEEAQEBCgERBQMBBQERNAKOAiwEZAwPAg8BDwElCq4BOB0NLAQJBwIOBpoB2AcEEQMNA3cEXwYMBAEPDAQ4CAoGKAgeAgJO1AIMDgINAwkHLgEHCA4ECQcJB5MBATclCoYI4M0CILogBt4BAoItDrE6D-4EohOeBOILyyYF4CDRuCsBHmCAAfABkPwD_v8DAv7_AwI"
14
+ };
15
+
16
+ // src/feedback-unicode-profile.codec.ts
17
+ var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
18
+ var BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
19
+ var MAX_UNICODE_ENDPOINT = 1114112;
20
+ var MAX_ENCODED_PAYLOAD_CHARACTERS = 8192;
21
+ var MAX_ULEB128_BYTES = 5;
22
+ var MAX_PROFILE_RANGE_COUNT = 707;
23
+ var MAX_PROFILE_ENDPOINT_COUNT = MAX_PROFILE_RANGE_COUNT * 2;
24
+ var MAX_PROFILE_CODE_POINT_COUNT = MAX_UNICODE_ENDPOINT;
25
+ var ARTIFACT_KEYS = /* @__PURE__ */ new Set([
26
+ "profileId",
27
+ "unicodeVersion",
28
+ "sourcePackage",
29
+ "sourcePackageVersion",
30
+ "sourceProperty",
31
+ "rangeEncoding",
32
+ "rangeCount",
33
+ "endpointCount",
34
+ "codePointCount",
35
+ "endpointSha256",
36
+ "payload"
37
+ ]);
38
+ function isRecord(value) {
39
+ return typeof value === "object" && value !== null && !Array.isArray(value);
40
+ }
41
+ function hasExpectedMetadata(artifact, expected) {
42
+ return Object.keys(artifact).length === ARTIFACT_KEYS.size && Object.keys(artifact).every((key) => ARTIFACT_KEYS.has(key)) && artifact.profileId === expected.profileId && artifact.unicodeVersion === expected.unicodeVersion && artifact.sourcePackage === expected.sourcePackage && artifact.sourcePackageVersion === expected.sourcePackageVersion && artifact.sourceProperty === expected.sourceProperty && artifact.rangeEncoding === expected.rangeEncoding && artifact.rangeCount === expected.rangeCount && artifact.endpointCount === expected.endpointCount && artifact.codePointCount === expected.codePointCount && artifact.endpointSha256 === expected.endpointSha256;
43
+ }
44
+ function isBoundedPositiveInteger(value, maximum) {
45
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= maximum;
46
+ }
47
+ function hasBoundedCounts(artifact, expected) {
48
+ return isBoundedPositiveInteger(
49
+ artifact.rangeCount,
50
+ MAX_PROFILE_RANGE_COUNT
51
+ ) && isBoundedPositiveInteger(
52
+ expected.rangeCount,
53
+ MAX_PROFILE_RANGE_COUNT
54
+ ) && isBoundedPositiveInteger(
55
+ artifact.endpointCount,
56
+ MAX_PROFILE_ENDPOINT_COUNT
57
+ ) && isBoundedPositiveInteger(
58
+ expected.endpointCount,
59
+ MAX_PROFILE_ENDPOINT_COUNT
60
+ ) && artifact.endpointCount === artifact.rangeCount * 2 && expected.endpointCount === expected.rangeCount * 2 && isBoundedPositiveInteger(
61
+ artifact.codePointCount,
62
+ MAX_PROFILE_CODE_POINT_COUNT
63
+ ) && isBoundedPositiveInteger(
64
+ expected.codePointCount,
65
+ MAX_PROFILE_CODE_POINT_COUNT
66
+ );
67
+ }
68
+ function decodeCanonicalBase64Url(value) {
69
+ if (value.length === 0 || value.length > MAX_ENCODED_PAYLOAD_CHARACTERS || value.length % 4 === 1 || !BASE64URL_PATTERN.test(value)) {
70
+ return void 0;
71
+ }
72
+ const finalSextet = BASE64URL_ALPHABET.indexOf(value.at(-1) ?? "");
73
+ if (finalSextet < 0 || value.length % 4 === 2 && (finalSextet & 15) !== 0 || value.length % 4 === 3 && (finalSextet & 3) !== 0) {
74
+ return void 0;
75
+ }
76
+ const expectedByteCount = Math.floor(value.length * 6 / 8);
77
+ const output = new Uint8Array(expectedByteCount);
78
+ let outputIndex = 0;
79
+ let accumulator = 0;
80
+ let bitCount = 0;
81
+ for (const character of value) {
82
+ const sextet = BASE64URL_ALPHABET.indexOf(character);
83
+ if (sextet < 0) {
84
+ return void 0;
85
+ }
86
+ accumulator = accumulator << 6 | sextet;
87
+ bitCount += 6;
88
+ while (bitCount >= 8) {
89
+ bitCount -= 8;
90
+ output[outputIndex] = accumulator >> bitCount & 255;
91
+ outputIndex += 1;
92
+ accumulator &= (1 << bitCount) - 1;
93
+ }
94
+ }
95
+ if (outputIndex !== expectedByteCount || bitCount > 0 && accumulator !== 0) {
96
+ return void 0;
97
+ }
98
+ return output;
99
+ }
100
+ function decodeCanonicalEndpointDeltas(bytes, endpointCount) {
101
+ if (!isBoundedPositiveInteger(
102
+ endpointCount,
103
+ MAX_PROFILE_ENDPOINT_COUNT
104
+ ) || endpointCount % 2 !== 0) {
105
+ return void 0;
106
+ }
107
+ const endpoints = new Uint32Array(endpointCount);
108
+ let byteOffset = 0;
109
+ let previousEndpoint = 0;
110
+ for (let endpointIndex = 0; endpointIndex < endpointCount; endpointIndex += 1) {
111
+ let delta = 0;
112
+ let multiplier = 1;
113
+ let byteCount = 0;
114
+ while (true) {
115
+ if (byteOffset >= bytes.length || byteCount >= MAX_ULEB128_BYTES) {
116
+ return void 0;
117
+ }
118
+ const byte = bytes[byteOffset];
119
+ if (byte === void 0) {
120
+ return void 0;
121
+ }
122
+ byteOffset += 1;
123
+ byteCount += 1;
124
+ const payload = byte & 127;
125
+ delta += payload * multiplier;
126
+ if (!Number.isSafeInteger(delta)) {
127
+ return void 0;
128
+ }
129
+ if ((byte & 128) === 0) {
130
+ if (byteCount > 1 && payload === 0) {
131
+ return void 0;
132
+ }
133
+ break;
134
+ }
135
+ multiplier *= 128;
136
+ }
137
+ const endpoint = previousEndpoint + delta;
138
+ if (delta <= 0 || !Number.isSafeInteger(endpoint) || endpoint > MAX_UNICODE_ENDPOINT) {
139
+ return void 0;
140
+ }
141
+ endpoints[endpointIndex] = endpoint;
142
+ previousEndpoint = endpoint;
143
+ }
144
+ if (byteOffset !== bytes.length) {
145
+ return void 0;
146
+ }
147
+ return endpoints;
148
+ }
149
+ function hasExpectedRanges(endpoints, expected) {
150
+ let previousEnd = -1;
151
+ let codePointCount = 0;
152
+ for (let index = 0; index < endpoints.length; index += 2) {
153
+ const start = endpoints[index];
154
+ const end = endpoints[index + 1];
155
+ if (start === void 0 || end === void 0 || start <= previousEnd || end <= start || end > MAX_UNICODE_ENDPOINT) {
156
+ return false;
157
+ }
158
+ codePointCount += end - start;
159
+ previousEnd = end;
160
+ }
161
+ return codePointCount === expected.codePointCount;
162
+ }
163
+ function decodeFeedbackUnicodeProfileArtifact(value, expected) {
164
+ if (!isRecord(value) || !hasBoundedCounts(value, expected) || !hasExpectedMetadata(value, expected) || typeof value.payload !== "string") {
165
+ return void 0;
166
+ }
167
+ const bytes = decodeCanonicalBase64Url(value.payload);
168
+ if (bytes === void 0) {
169
+ return void 0;
170
+ }
171
+ const endpoints = decodeCanonicalEndpointDeltas(
172
+ bytes,
173
+ expected.endpointCount
174
+ );
175
+ return endpoints !== void 0 && hasExpectedRanges(endpoints, expected) ? endpoints : void 0;
176
+ }
177
+
178
+ // src/feedback-unicode-profile.ts
179
+ var FEEDBACK_UNICODE_PROFILE_ID = "unicode-15.1.0-nfkc-v1";
180
+ var FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256 = "591e457524d6b4b988aa7c7687e76c95a78370fea38bbd1b09085be7fd935ef3";
181
+ var MAX_UNICODE_CODE_POINT = 1114111;
182
+ var HIGH_SURROGATE_START = 55296;
183
+ var HIGH_SURROGATE_END = 56319;
184
+ var LOW_SURROGATE_START = 56320;
185
+ var LOW_SURROGATE_END = 57343;
186
+ var UNASSIGNED_RANGE_ENDPOINTS = decodeFeedbackUnicodeProfileArtifact(
187
+ feedback_unicode_15_1_0_unassigned_default,
188
+ {
189
+ profileId: FEEDBACK_UNICODE_PROFILE_ID,
190
+ unicodeVersion: "15.1.0",
191
+ sourcePackage: "@unicode/unicode-15.1.0",
192
+ sourcePackageVersion: "1.6.17",
193
+ sourceProperty: "General_Category/Unassigned",
194
+ rangeEncoding: "flat-half-open-delta-uleb128-base64url-v1",
195
+ rangeCount: 707,
196
+ endpointCount: 1414,
197
+ codePointCount: 824718,
198
+ endpointSha256: FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256
199
+ }
200
+ );
201
+ function isFeedbackUnicodeProfileUnassigned(codePoint) {
202
+ if (UNASSIGNED_RANGE_ENDPOINTS === void 0 || !Number.isInteger(codePoint) || codePoint < 0 || codePoint > MAX_UNICODE_CODE_POINT || codePoint >= HIGH_SURROGATE_START && codePoint <= LOW_SURROGATE_END) {
203
+ return true;
204
+ }
205
+ let lowerRange = 0;
206
+ let upperRange = UNASSIGNED_RANGE_ENDPOINTS.length / 2 - 1;
207
+ while (lowerRange <= upperRange) {
208
+ const rangeIndex = Math.floor((lowerRange + upperRange) / 2);
209
+ const start = UNASSIGNED_RANGE_ENDPOINTS[rangeIndex * 2];
210
+ const end = UNASSIGNED_RANGE_ENDPOINTS[rangeIndex * 2 + 1];
211
+ if (start === void 0 || end === void 0) {
212
+ return true;
213
+ }
214
+ if (codePoint < start) {
215
+ upperRange = rangeIndex - 1;
216
+ } else if (codePoint >= end) {
217
+ lowerRange = rangeIndex + 1;
218
+ } else {
219
+ return true;
220
+ }
221
+ }
222
+ return false;
223
+ }
224
+ function containsFeedbackUnicodeProfileUnsupportedText(text) {
225
+ if (typeof text !== "string") {
226
+ return true;
227
+ }
228
+ for (let index = 0; index < text.length; index += 1) {
229
+ const first = text.charCodeAt(index);
230
+ let codePoint = first;
231
+ if (first >= HIGH_SURROGATE_START && first <= HIGH_SURROGATE_END) {
232
+ const second = text.charCodeAt(index + 1);
233
+ if (!Number.isInteger(second) || second < LOW_SURROGATE_START || second > LOW_SURROGATE_END) {
234
+ return true;
235
+ }
236
+ codePoint = 65536 + (first - HIGH_SURROGATE_START << 10) + (second - LOW_SURROGATE_START);
237
+ index += 1;
238
+ } else if (first >= LOW_SURROGATE_START && first <= LOW_SURROGATE_END) {
239
+ return true;
240
+ }
241
+ if (isFeedbackUnicodeProfileUnassigned(codePoint)) {
242
+ return true;
243
+ }
244
+ }
245
+ return false;
246
+ }
247
+
248
+ export {
249
+ FEEDBACK_UNICODE_PROFILE_ID,
250
+ FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256,
251
+ isFeedbackUnicodeProfileUnassigned,
252
+ containsFeedbackUnicodeProfileUnsupportedText
253
+ };
254
+ //# sourceMappingURL=chunk-6FE4FLKF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../unicode/feedback-unicode-15.1.0-unassigned.json","../src/feedback-unicode-profile.codec.ts","../src/feedback-unicode-profile.ts"],"sourcesContent":["{\n \"profileId\": \"unicode-15.1.0-nfkc-v1\",\n \"unicodeVersion\": \"15.1.0\",\n \"sourcePackage\": \"@unicode/unicode-15.1.0\",\n \"sourcePackageVersion\": \"1.6.17\",\n \"sourceProperty\": \"General_Category/Unassigned\",\n \"rangeEncoding\": \"flat-half-open-delta-uleb128-base64url-v1\",\n \"rangeCount\": 707,\n \"endpointCount\": 1414,\n \"codePointCount\": 824718,\n \"endpointSha256\": \"591e457524d6b4b988aa7c7687e76c95a78370fea38bbd1b09085be7fd935ef3\",\n \"payload\": \"-AYCBgQHAQEBFAGNAwEmAjICAwE3CBsEBguOAgE8AmUOOwIxAg8BHAIBAQsFHwECBuwBAQgCAgIWAQcBAQMEAgkCAgIECAEEAgEFAhkCAwEGBAICFgEHAQIBAgECAgEBBQQCAgMDAQcEAQEHEQoDAQkBAwEWAQcBAgEFAgoBAwEDAgEPBAIMBwcBAwEIAgICFgEHAQIBBQIJAgICAwcDBAIBBQISCgIBBgMDAQQDAgEBAQIDAgMDAwwEBQMDAQQCAQYBDhUFDQEDARcBEAIJAQMBBAcCAQMCAQIEAgoHFgEDARcBCgEFAgkBAwEEBwIGAgEEAgoBAwwNAQMBMwEDAQYEEAIaAQMBEgMYAQkBAQIHAwEEBgEBAQgGCgIDDDoEHSUCAQEBBQEYAQEBFwIFAQEBBwEKAgQgSAEkBCcBJAEPAQ0lxgEBAQUBAvkCAQQCBwEBAQQCKQEEAiEBBAIHAQEBBAIPATkBBAJDAiADGgZWAgYCnQUDWQcWCRgJFAwNAQMBAgxeAgoGCgYaBlkHKwVGCh8BDAQMBAEDKgIFCywEGgYLAz4CQQEdAgsGCgYOAh8xTQMvAXQIPAMPAzwHKwILCCsFlgQCBgImAgYCCAEBAQEBAQEfAjUBDwEOAgYBEwIDAQkBZQEMAhsBDQMhDyEPjAEElwUZCxWUDgIgAd0CBS0BAQUBAjgHAg4YCQcBBwEHAQcBBwEHAQcBBwF-IhoBWQzWARpQAVYCZwUrAV4BVAswAe3kAQM3CdwCFLgBCMsBBQIBAQEFGDsDCgY4CEYIDAZ0Cx4DTgELBCEBNwkOAgoCZxgcCgYCBgIGCQcBBwE8BH4CCgakVwwXBDEE7kQCaiYHDAUFGgEFAQEBAgECAX0QvQMCNgcBICoGMwETAQQEBQGHAQIBAb4BAwYCBgIGAgMDBwEHCgUCDAEaARMBAgEPAg4iewUDBC0DWAENAwEvLoIBHQMxDxwEJAkeBSsFHgElBA4qngECCgYkBCQEKAg0CwwBDwEHAQIBCwEPAQcBAkO3AgkWCggYBgEqAQlFBgIBASwBAgMBAhcBSAgJMBMBAgUhAxsFAUA4BBQCMgECBQgBAwEdAgMECgcJB0AgJwQMCTYDHQIbBRoHBAwHUEk3Mw0zBy4ICqYCHwEqAQMCAksrCCoWGiYcFBcJTgQkCUQKAQIZBwoGNQESCCcJYAEUCxIBLz4HAQEBBAEPAQsGOwUKBgQBCAICAhYBBwECAQUBCgICAgMCAQYBBQcCBwMFiwFcAQUeSAgKpgE2AiYiRQsKBg0TOgYKNhsCDwQXuQE8ZFMMCAIBAggBAgEeAQICDAkKRggCLgILG0gIUw1JBwr2AQkBLQEOCh0DIAIWAQ5JBwECASwDAQECAQkICgYGAQIBJQECAQYHCrYCGQcRASkDHFYBDzINmwdmbwEFC8QBzBRjDdYIqh_HBLlDuQQHHwEKBFEBCgYeAgYKRgoKAQcBFQUTsAVbZUsEOQcRQAULAg74LwjWCSoJ50UEAQcBAgGjAg8BHQMCAQ4ECIwDhBJrBQ0DCQcKAgjcJC4CFwl0PPYBCicCwgEVRnoUDBQMVwkZhwFVAUcBAgIBAgICBAEMAQEBBwFBAQQCCAEHARwBBAEFAQEDBwHUAgKkAgK-BQ8FAQ_QCB8GBtUBBwERAgcBAgEFBT4hAXAtAw4CCgQCwAIfEToFAdADKuYFBwEEAQIBDwHFAQIQKUwECgQCkQZETD3CAQQBGwECAQECAQEKAQQBAQEBBgEEAQEBAQEBAwECAQECAQEBAQEBAQEBAQIBAQIEAQcBBAEEAQEBCgERBQMBBQERNAKOAiwEZAwPAg8BDwElCq4BOB0NLAQJBwIOBpoB2AcEEQMNA3cEXwYMBAEPDAQ4CAoGKAgeAgJO1AIMDgINAwkHLgEHCA4ECQcJB5MBATclCoYI4M0CILogBt4BAoItDrE6D-4EohOeBOILyyYF4CDRuCsBHmCAAfABkPwD_v8DAv7_AwI\"\n}\n","const BASE64URL_ALPHABET =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;\nconst MAX_UNICODE_ENDPOINT = 0x110000;\nconst MAX_ENCODED_PAYLOAD_CHARACTERS = 8_192;\nconst MAX_ULEB128_BYTES = 5;\nconst MAX_PROFILE_RANGE_COUNT = 707;\nconst MAX_PROFILE_ENDPOINT_COUNT = MAX_PROFILE_RANGE_COUNT * 2;\nconst MAX_PROFILE_CODE_POINT_COUNT = MAX_UNICODE_ENDPOINT;\nconst ARTIFACT_KEYS = new Set([\n \"profileId\",\n \"unicodeVersion\",\n \"sourcePackage\",\n \"sourcePackageVersion\",\n \"sourceProperty\",\n \"rangeEncoding\",\n \"rangeCount\",\n \"endpointCount\",\n \"codePointCount\",\n \"endpointSha256\",\n \"payload\",\n]);\n\nexport interface FeedbackUnicodeProfileExpectation {\n readonly profileId: string;\n readonly unicodeVersion: string;\n readonly sourcePackage: string;\n readonly sourcePackageVersion: string;\n readonly sourceProperty: string;\n readonly rangeEncoding: string;\n readonly rangeCount: number;\n readonly endpointCount: number;\n readonly codePointCount: number;\n readonly endpointSha256: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasExpectedMetadata(\n artifact: Record<string, unknown>,\n expected: FeedbackUnicodeProfileExpectation,\n): boolean {\n return (\n Object.keys(artifact).length === ARTIFACT_KEYS.size &&\n Object.keys(artifact).every((key) => ARTIFACT_KEYS.has(key)) &&\n artifact.profileId === expected.profileId &&\n artifact.unicodeVersion === expected.unicodeVersion &&\n artifact.sourcePackage === expected.sourcePackage &&\n artifact.sourcePackageVersion === expected.sourcePackageVersion &&\n artifact.sourceProperty === expected.sourceProperty &&\n artifact.rangeEncoding === expected.rangeEncoding &&\n artifact.rangeCount === expected.rangeCount &&\n artifact.endpointCount === expected.endpointCount &&\n artifact.codePointCount === expected.codePointCount &&\n artifact.endpointSha256 === expected.endpointSha256\n );\n}\n\nfunction isBoundedPositiveInteger(\n value: unknown,\n maximum: number,\n): value is number {\n return (\n typeof value === \"number\" &&\n Number.isSafeInteger(value) &&\n value > 0 &&\n value <= maximum\n );\n}\n\nfunction hasBoundedCounts(\n artifact: Record<string, unknown>,\n expected: FeedbackUnicodeProfileExpectation,\n): boolean {\n return (\n isBoundedPositiveInteger(\n artifact.rangeCount,\n MAX_PROFILE_RANGE_COUNT,\n ) &&\n isBoundedPositiveInteger(\n expected.rangeCount,\n MAX_PROFILE_RANGE_COUNT,\n ) &&\n isBoundedPositiveInteger(\n artifact.endpointCount,\n MAX_PROFILE_ENDPOINT_COUNT,\n ) &&\n isBoundedPositiveInteger(\n expected.endpointCount,\n MAX_PROFILE_ENDPOINT_COUNT,\n ) &&\n artifact.endpointCount === artifact.rangeCount * 2 &&\n expected.endpointCount === expected.rangeCount * 2 &&\n isBoundedPositiveInteger(\n artifact.codePointCount,\n MAX_PROFILE_CODE_POINT_COUNT,\n ) &&\n isBoundedPositiveInteger(\n expected.codePointCount,\n MAX_PROFILE_CODE_POINT_COUNT,\n )\n );\n}\n\nfunction decodeCanonicalBase64Url(value: string): Uint8Array | undefined {\n if (\n value.length === 0 ||\n value.length > MAX_ENCODED_PAYLOAD_CHARACTERS ||\n value.length % 4 === 1 ||\n !BASE64URL_PATTERN.test(value)\n ) {\n return undefined;\n }\n\n const finalSextet = BASE64URL_ALPHABET.indexOf(value.at(-1) ?? \"\");\n if (\n finalSextet < 0 ||\n (value.length % 4 === 2 && (finalSextet & 0b1111) !== 0) ||\n (value.length % 4 === 3 && (finalSextet & 0b11) !== 0)\n ) {\n return undefined;\n }\n\n const expectedByteCount = Math.floor((value.length * 6) / 8);\n const output = new Uint8Array(expectedByteCount);\n let outputIndex = 0;\n let accumulator = 0;\n let bitCount = 0;\n\n for (const character of value) {\n const sextet = BASE64URL_ALPHABET.indexOf(character);\n if (sextet < 0) {\n return undefined;\n }\n accumulator = (accumulator << 6) | sextet;\n bitCount += 6;\n while (bitCount >= 8) {\n bitCount -= 8;\n output[outputIndex] = (accumulator >> bitCount) & 0xff;\n outputIndex += 1;\n accumulator &= (1 << bitCount) - 1;\n }\n }\n\n if (\n outputIndex !== expectedByteCount ||\n (bitCount > 0 && accumulator !== 0)\n ) {\n return undefined;\n }\n return output;\n}\n\nfunction decodeCanonicalEndpointDeltas(\n bytes: Uint8Array,\n endpointCount: number,\n): Uint32Array | undefined {\n if (\n !isBoundedPositiveInteger(\n endpointCount,\n MAX_PROFILE_ENDPOINT_COUNT,\n ) ||\n endpointCount % 2 !== 0\n ) {\n return undefined;\n }\n const endpoints = new Uint32Array(endpointCount);\n let byteOffset = 0;\n let previousEndpoint = 0;\n\n for (let endpointIndex = 0; endpointIndex < endpointCount; endpointIndex += 1) {\n let delta = 0;\n let multiplier = 1;\n let byteCount = 0;\n\n while (true) {\n if (byteOffset >= bytes.length || byteCount >= MAX_ULEB128_BYTES) {\n return undefined;\n }\n const byte = bytes[byteOffset];\n if (byte === undefined) {\n return undefined;\n }\n byteOffset += 1;\n byteCount += 1;\n\n const payload = byte & 0x7f;\n delta += payload * multiplier;\n if (!Number.isSafeInteger(delta)) {\n return undefined;\n }\n\n if ((byte & 0x80) === 0) {\n if (byteCount > 1 && payload === 0) {\n return undefined;\n }\n break;\n }\n multiplier *= 128;\n }\n\n const endpoint = previousEndpoint + delta;\n if (\n delta <= 0 ||\n !Number.isSafeInteger(endpoint) ||\n endpoint > MAX_UNICODE_ENDPOINT\n ) {\n return undefined;\n }\n endpoints[endpointIndex] = endpoint;\n previousEndpoint = endpoint;\n }\n\n if (byteOffset !== bytes.length) {\n return undefined;\n }\n return endpoints;\n}\n\nfunction hasExpectedRanges(\n endpoints: Uint32Array,\n expected: FeedbackUnicodeProfileExpectation,\n): boolean {\n let previousEnd = -1;\n let codePointCount = 0;\n\n for (let index = 0; index < endpoints.length; index += 2) {\n const start = endpoints[index];\n const end = endpoints[index + 1];\n if (\n start === undefined ||\n end === undefined ||\n start <= previousEnd ||\n end <= start ||\n end > MAX_UNICODE_ENDPOINT\n ) {\n return false;\n }\n codePointCount += end - start;\n previousEnd = end;\n }\n\n return codePointCount === expected.codePointCount;\n}\n\n/**\n * Decodes and validates the checked-in Unicode profile artifact.\n *\n * This is an internal supply-boundary helper. It deliberately returns no\n * partial data: malformed base64url, non-shortest ULEB128, overflow, range\n * disorder, count mismatch, trailing bytes, or metadata drift all fail\n * closed.\n */\nexport function decodeFeedbackUnicodeProfileArtifact(\n value: unknown,\n expected: FeedbackUnicodeProfileExpectation,\n): Uint32Array | undefined {\n if (\n !isRecord(value) ||\n !hasBoundedCounts(value, expected) ||\n !hasExpectedMetadata(value, expected) ||\n typeof value.payload !== \"string\"\n ) {\n return undefined;\n }\n\n const bytes = decodeCanonicalBase64Url(value.payload);\n if (bytes === undefined) {\n return undefined;\n }\n const endpoints = decodeCanonicalEndpointDeltas(\n bytes,\n expected.endpointCount,\n );\n return endpoints !== undefined && hasExpectedRanges(endpoints, expected)\n ? endpoints\n : undefined;\n}\n","import profileArtifact from \"../unicode/feedback-unicode-15.1.0-unassigned.json\" with {\n type: \"json\",\n};\nimport { decodeFeedbackUnicodeProfileArtifact } from \"./feedback-unicode-profile.codec.js\";\n\n/** Canonical Unicode and normalization profile for transient feedback text. */\nexport const FEEDBACK_UNICODE_PROFILE_ID =\n \"unicode-15.1.0-nfkc-v1\" as const;\n\n/**\n * SHA-256 of the flattened half-open range endpoints encoded as unsigned\n * 32-bit big-endian integers.\n */\nexport const FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256 =\n \"591e457524d6b4b988aa7c7687e76c95a78370fea38bbd1b09085be7fd935ef3\" as const;\n\nconst MAX_UNICODE_CODE_POINT = 0x10ffff;\nconst HIGH_SURROGATE_START = 0xd800;\nconst HIGH_SURROGATE_END = 0xdbff;\nconst LOW_SURROGATE_START = 0xdc00;\nconst LOW_SURROGATE_END = 0xdfff;\n\nconst UNASSIGNED_RANGE_ENDPOINTS = decodeFeedbackUnicodeProfileArtifact(\n profileArtifact,\n {\n profileId: FEEDBACK_UNICODE_PROFILE_ID,\n unicodeVersion: \"15.1.0\",\n sourcePackage: \"@unicode/unicode-15.1.0\",\n sourcePackageVersion: \"1.6.17\",\n sourceProperty: \"General_Category/Unassigned\",\n rangeEncoding: \"flat-half-open-delta-uleb128-base64url-v1\",\n rangeCount: 707,\n endpointCount: 1_414,\n codePointCount: 824_718,\n endpointSha256: FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256,\n },\n);\n\n/**\n * Reports whether a numeric code point is unsupported by the pinned feedback\n * profile.\n *\n * Unicode 15.1 `General_Category=Unassigned` values return `true`. Invalid\n * numeric values and surrogate code points also return `true` so validation\n * callers fail closed.\n */\nexport function isFeedbackUnicodeProfileUnassigned(\n codePoint: number,\n): boolean {\n if (\n UNASSIGNED_RANGE_ENDPOINTS === undefined ||\n !Number.isInteger(codePoint) ||\n codePoint < 0 ||\n codePoint > MAX_UNICODE_CODE_POINT ||\n (codePoint >= HIGH_SURROGATE_START &&\n codePoint <= LOW_SURROGATE_END)\n ) {\n return true;\n }\n\n let lowerRange = 0;\n let upperRange = UNASSIGNED_RANGE_ENDPOINTS.length / 2 - 1;\n\n while (lowerRange <= upperRange) {\n const rangeIndex = Math.floor((lowerRange + upperRange) / 2);\n const start = UNASSIGNED_RANGE_ENDPOINTS[rangeIndex * 2];\n const end = UNASSIGNED_RANGE_ENDPOINTS[rangeIndex * 2 + 1];\n\n if (start === undefined || end === undefined) {\n return true;\n }\n if (codePoint < start) {\n upperRange = rangeIndex - 1;\n } else if (codePoint >= end) {\n lowerRange = rangeIndex + 1;\n } else {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Reports whether text contains a Unicode 15.1-unassigned code point or a\n * lone UTF-16 surrogate.\n *\n * The scan uses code units deliberately so malformed strings cannot be\n * silently replaced or normalized before profile enforcement. Callers must\n * apply their own UTF-16 length bound before passing untrusted text; this\n * generic helper intentionally does not impose a product-specific limit.\n */\nexport function containsFeedbackUnicodeProfileUnsupportedText(\n text: string,\n): boolean {\n if (typeof text !== \"string\") {\n return true;\n }\n\n for (let index = 0; index < text.length; index += 1) {\n const first = text.charCodeAt(index);\n let codePoint = first;\n\n if (first >= HIGH_SURROGATE_START && first <= HIGH_SURROGATE_END) {\n const second = text.charCodeAt(index + 1);\n if (\n !Number.isInteger(second) ||\n second < LOW_SURROGATE_START ||\n second > LOW_SURROGATE_END\n ) {\n return true;\n }\n codePoint =\n 0x10000 +\n ((first - HIGH_SURROGATE_START) << 10) +\n (second - LOW_SURROGATE_START);\n index += 1;\n } else if (first >= LOW_SURROGATE_START && first <= LOW_SURROGATE_END) {\n return true;\n }\n\n if (isFeedbackUnicodeProfileUnassigned(codePoint)) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":"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","names":[]}