@plasius/schema 1.2.18 → 1.3.1

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 +207 -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 +2258 -14
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +987 -286
  32. package/dist/index.d.ts +987 -286
  33. package/dist/index.js +1532 -2197
  34. package/dist/index.js.map +1 -1
  35. package/package.json +36 -12
  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.
@@ -30,6 +47,171 @@ uses `dist-cjs/*.js` with `type: module`, it must generate
30
47
 
31
48
  ---
32
49
 
50
+ ## Player-system event contracts
51
+
52
+ The package exports versioned contracts for a blob-backed, privacy-safe player
53
+ event pipeline:
54
+
55
+ ```ts
56
+ import {
57
+ PlayerSystemCuratedSnapshotSchema,
58
+ PlayerSystemNormalizedEventBatchSchema,
59
+ PlayerSystemObservedEventBatchSchema,
60
+ } from "@plasius/schema";
61
+ ```
62
+
63
+ The contracts cover raw observed-event ingress, normalized batches with
64
+ deterministic keys, and curated per-player Event Log/Achievement snapshots.
65
+ Only the stable `playerId` is treated as a personal identifier. It is retained
66
+ for storage partitioning and pseudonymized by `sanitizeForLog`; unknown fields
67
+ and hidden server truth are excluded by schema serialization.
68
+
69
+ ## Privacy-safe feedback contracts
70
+
71
+ The package exports closed, versioned contracts for feedback intake and
72
+ reporting:
73
+
74
+ ```ts
75
+ import {
76
+ FeedbackBugPacketSchema,
77
+ FeedbackPublicSummarySchema,
78
+ FeedbackRichTextAstSchema,
79
+ FeedbackReviewPacketSchema,
80
+ FeedbackTransientAnalysisRequestSchema,
81
+ validateFeedbackTransientAnalysisRequest,
82
+ } from "@plasius/schema";
83
+ import type {
84
+ FeedbackBugTransientAnalysisRequest,
85
+ FeedbackReviewTransientAnalysisRequest,
86
+ } from "@plasius/schema";
87
+ ```
88
+
89
+ Renderer and browser packages that need only the game-diagnostics vocabulary
90
+ should import `@plasius/schema/feedback-diagnostics-vocabulary`. This
91
+ lightweight entrypoint exports the canonical surface/provenance pairs, buckets,
92
+ feature/counter/error allowlists, bounds, and discriminated type without
93
+ loading the schema builder. Server validation and analysis tools should import
94
+ `@plasius/schema/feedback-diagnostics`, which adds the canonical runtime schema.
95
+ Both entrypoints derive from the same source definitions and preserve
96
+ structural parity in ESM and CommonJS builds, so consumers cannot maintain a
97
+ second diagnostics dialect. Use `validateFeedbackGameDiagnostics()` when a
98
+ consumer needs a validation result whose `value` retains the correlated
99
+ `FeedbackGameDiagnostics` TypeScript type.
100
+
101
+ Free-form feedback exists only in `FeedbackRichTextAstSchema`, which is
102
+ intended for a transient no-retention scanner. Its text nodes are marked
103
+ high-PII, clear-on-storage, and omit-from-logs. The interoperable document is
104
+ rooted at
105
+ `{ type: "doc", version: "1.0.0", schemaVersion: "1", children }`; it permits
106
+ only paragraph and bullet-list-item blocks with bounded indentation and text
107
+ nodes with bold, italic, and underline marks. Narrative must already be NFKC
108
+ normalised against the exported `unicode-15.1.0-nfkc-v1` profile before
109
+ validation. Assignment is checked against the original code units before host
110
+ NFKC is invoked. Its 4,000-character ceiling counts Unicode code points and
111
+ inter-block newlines consistently with the private scanner, with a separate
112
+ 8,000 UTF-16-unit safety ceiling. HTML, Markdown links, protocol-relative
113
+ links, and `http`, `https`, `ftp`, `mailto`, `javascript`, `data`, `blob`, and
114
+ `file` schemes are rejected.
115
+
116
+ `FeedbackTransientAnalysisRequestSchema` is closed and discriminated by
117
+ `purpose`. A bug analysis request must carry a closed `surfaceId`; the service
118
+ must verify it against the caller's capability-projected catalog before
119
+ handing ciphertext to the private scanner. A review analysis request must omit
120
+ `surfaceId`. The exported bug/review request types preserve the same
121
+ discriminator for TypeScript consumers;
122
+ `validateFeedbackTransientAnalysisRequest()` preserves that union on
123
+ successful runtime validation.
124
+
125
+ Final bug and review JSON bodies deliberately contain no `submissionId`.
126
+ Consuming HTTP APIs require `Idempotency-Key` as the sole final-submission
127
+ correlation mechanism and keep it in the isolated abuse/idempotency control
128
+ plane. It must never be copied into a draft, accepted packet, report, log, or
129
+ public/Admin/MCP projection.
130
+
131
+ The complete Unicode 15.1 unassigned corpus is shipped independently of the
132
+ host runtime:
133
+
134
+ ```ts
135
+ import {
136
+ FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256,
137
+ FEEDBACK_UNICODE_PROFILE_ID,
138
+ containsFeedbackUnicodeProfileUnsupportedText,
139
+ isFeedbackUnicodeProfileUnassigned,
140
+ } from "@plasius/schema/feedback-unicode-profile";
141
+ ```
142
+
143
+ `containsFeedbackUnicodeProfileUnsupportedText` rejects every code point
144
+ unassigned in Unicode 15.1 and every lone UTF-16 surrogate. The code-point
145
+ helper decodes canonical, delta ULEB128/base64url once and uses binary search
146
+ over 707 half-open ranges. Non-JavaScript scanners can consume the same
147
+ language-neutral corpus from
148
+ `@plasius/schema/unicode/feedback-unicode-15.1.0-unassigned.json` and verify
149
+ its published endpoint digest. The corpus is generated from the exact
150
+ build-time dependency `@unicode/unicode-15.1.0@1.6.17`; run
151
+ `npm run unicode:check` to prove the checked-in artifact is current. See
152
+ [ADR-0007](./docs/adrs/adr-0007-pinned-unicode-feedback-profile.md) and
153
+ [third-party notices](./THIRD_PARTY_NOTICES.md).
154
+
155
+ Validated reusable fragments retain exact `type` and `version` metadata when
156
+ embedded in parent contracts. This makes the output of the encrypted-envelope,
157
+ derived-analysis, and game-diagnostics schemas directly composable into their
158
+ request or packet schemas without a metadata-stripping step.
159
+
160
+ Accepted packet, diagnostics, report, checkpoint, and public-summary schemas
161
+ cannot express narrative, reporter identity or pseudonyms, network/client
162
+ metadata, arbitrary URLs, pixels, quotes, summaries, embeddings, content
163
+ hashes, or model traces. Game surfaces, provenance contracts, features,
164
+ counters, and error codes are closed enums rather than attacker-controlled
165
+ safe-looking strings. The public summary omits counts, averages, and trends
166
+ entirely when the ten-review privacy threshold is not met and always carries
167
+ 13 canonical UTC week slots, using suppressed gaps where a week is below the
168
+ same threshold. A trend is emitted only with a preceding 90-day comparison
169
+ that independently meets the ten-review threshold; the schema derives its
170
+ delta and enforces `up`/`down` only at a change of at least 0.1 stars.
171
+
172
+ Hourly reports bind targets to the closed surface registry and include
173
+ renderer/backend/viewport/frame-rate/frame-time plus closed diagnostic
174
+ feature/counter/error distributions. Release IDs and build IDs are
175
+ server-registry values: intake clients never submit them, and consumers must
176
+ resolve them from trusted deployment metadata before packet construction.
177
+ Advisory trigger counts and ordered recommendation IDs are derived exactly
178
+ from the report distributions and processor lag rather than accepted as
179
+ free-standing claims. Feedback context caps remaining bug cooldown at 24
180
+ hours and review suppression at 30 days; acceptance receipts distinguish bugs
181
+ from reviews and accept only the five-step bug ladder or exact 30-day review
182
+ period.
183
+
184
+ All feedback contracts opt in to recursive unknown-field rejection, including
185
+ direct references and arrays of references. Unshaped references admit only
186
+ `type` and `id`; shaped references admit only those keys plus their declared
187
+ fields. Closed feedback vocabularies and definition objects are runtime frozen
188
+ before schemas capture them. Before recursive cloning, strict validation also
189
+ performs an iterative whole-value node/depth budget so malformed containers
190
+ hidden under scalar fields cannot exhaust the call stack. The
191
+ general schema default remains backwards-compatible stripping:
192
+
193
+ ```ts
194
+ const ClosedPacket = createSchema(fields, "closed-packet", {
195
+ version: "1.0.0",
196
+ piiEnforcement: "strict",
197
+ unknownFields: "reject",
198
+ identity: "exact",
199
+ });
200
+ ```
201
+
202
+ Schema-level validators receive an optional, frozen
203
+ `SchemaValidationContext` with only `wasProvided(fieldName)`. This lets a
204
+ closed discriminator distinguish omission from an explicit `null` without
205
+ giving validation hooks access to raw field values.
206
+
207
+ Unknown-field errors name only the schema-owned container and never echo an
208
+ untrusted key or value. Exact identity rejects a mismatched contract type or
209
+ version. The schemas constrain data shape; consumers must still
210
+ perform the documented private PII scan, discard narrative immediately, and
211
+ validate again before every storage write. See the
212
+ [feedback contract design](./docs/design/feedback-contracts.md) and
213
+ [ADR-0006](./docs/adrs/adr-0006-privacy-safe-feedback-contract-boundaries.md).
214
+
33
215
  ## Demo
34
216
 
35
217
  ```bash
@@ -43,7 +225,7 @@ See `demo/README.md` for the local sanity-check scaffold.
43
225
 
44
226
  ## Node.js Version
45
227
 
46
- This project uses Node.js **22** by default. The version is pinned in the [`.nvmrc`](./.nvmrc) file.
228
+ This project uses Node.js **24** by default. The version is pinned in the [`.nvmrc`](./.nvmrc) file.
47
229
 
48
230
  If you use [nvm](https://github.com/nvm-sh/nvm), simply run:
49
231
 
@@ -284,3 +466,27 @@ We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for gu
284
466
  ## License
285
467
 
286
468
  Licensed under the [Apache-2.0 License](./LICENSE).
469
+
470
+ <!-- BEGIN PLASIUS RELEASE INTEGRITY -->
471
+ ## Release integrity
472
+
473
+ CI keeps the administrative contributor registry outside Git and npm package
474
+ artifacts using exact, case-normalised path checks. Pull requests run on
475
+ GitHub-hosted runners after same-repository admission; protected `main` CI uses
476
+ the workflow-restricted self-hosted group. Release preparation and npm
477
+ publication use GitHub-hosted runners with Node.js 24.18.0 LTS.
478
+
479
+ Dispatch `cd.yml` from protected `main` with `phase: prepare`. Release metadata
480
+ lands through a unique pull request. After successful push-triggered CI for the
481
+ exact merge SHA, the workflow dispatches a separate `publish` run from that
482
+ same SHA. Publication fails closed if `main`, CI evidence, the version, tag,
483
+ prerelease identity, artifact digests, or npm registry integrity differ.
484
+
485
+ The read-only validation job installs dependencies, validates the package,
486
+ builds an SBOM, and seals an immutable tarball. The `production` job runs no
487
+ package lifecycle or dependency code; it verifies the exact artifact hand-off
488
+ and publishes that tarball through npm OIDC with provenance. No npm write token
489
+ or fallback is configured. The trusted publisher must be bound to
490
+ `Plasius-LTD/schema`, `cd.yml`, `production`, and `npm publish`. Rollback is to
491
+ disable `cd.yml`; never restore token-based publication.
492
+ <!-- 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":[]}