diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0a41a6c..318c5b2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -306,30 +306,20 @@ jobs: raise SystemExit( f"{consumer} did not resolve protocol-owned wcag22-feasibility" ) - # #296-C2: атомарная explicit-операция protocol существует за - # non-default фичей. До #296-C3 её резолвит ТОЛЬКО нативный - # conformance-генератор; публикуемые adapters (compiler, ffi) - # обязаны оставаться feasibility-only. - if consumer == "labcolors-conformance": - if ( - 'labcolors-protocol feature "wcag22-explicit-selection"' - not in feature_tree - ): - raise SystemExit( - "labcolors-conformance must resolve the native-only " - "explicit-selection protocol feature" - ) - if ( - 'labcolors-core feature "wcag22-explicit-feasibility"' - not in feature_tree - ): - raise SystemExit( - "labcolors-conformance must resolve the explicit-domain " - "capability for pack generation" - ) - elif 'labcolors-core feature "wcag22-explicit-feasibility"' in feature_tree: + # #296-C3: атомарная explicit-операция опубликована. Каждый + # protocol-потребитель (compiler, ffi, conformance) обязан + # резолвить её через одну non-default protocol-фичу; прямых + # core-фич у потребителей по-прежнему нет. + if ( + 'labcolors-protocol feature "wcag22-explicit-selection"' + not in feature_tree + ): raise SystemExit( - f"{consumer} resolved the pre-C3 native-only explicit capability" + f"{consumer} must resolve the explicit-selection protocol feature" + ) + if 'labcolors-core feature "wcag22-explicit-feasibility"' not in feature_tree: + raise SystemExit( + f"{consumer} did not resolve the explicit-domain capability" ) runtime_dependencies = packages["labcolors-wasm"]["dependencies"] @@ -370,8 +360,8 @@ jobs: raise SystemExit("compiler role resolved the theme/runtime engine") if 'labcolors-core feature "wcag22-feasibility"' not in compiler_tree: raise SystemExit("compiler role lacks protocol-owned wcag22-feasibility") - if 'labcolors-core feature "wcag22-explicit-feasibility"' in compiler_tree: - raise SystemExit("compiler role resolved the Core-only explicit capability") + if 'labcolors-core feature "wcag22-explicit-feasibility"' not in compiler_tree: + raise SystemExit("compiler role lacks the published explicit capability") print("core capability projection: PASS") PY @@ -639,11 +629,11 @@ jobs: node-version: ${{ env.NODE_TOOLCHAIN }} cache: npm cache-dependency-path: packages/colors/package-lock.json - - name: verify committed #296-C2 canonical whole-call compiler evidence + - name: verify committed #296-C3 canonical whole-call compiler evidence working-directory: packages/colors run: >- node bench/wcag22-feasibility-boundary.bench.mjs - --verify bench/wcag22-feasibility-wasm-boundary-v4.json + --verify bench/wcag22-feasibility-wasm-boundary-v5.json - name: independently fingerprint both execution-role WASM artifacts run: | for artifact in \ @@ -654,13 +644,13 @@ jobs: sha256="$(sha256sum "$artifact" | cut -d ' ' -f1)" echo "canonical candidate artifact=${artifact} raw=${bytes}B sha256=${sha256}" done - sha256sum packages/colors/bench/wcag22-feasibility-wasm-boundary-v4.json - - name: upload exact #296-C2 whole-call evidence + sha256sum packages/colors/bench/wcag22-feasibility-wasm-boundary-v5.json + - name: upload exact #296-C3 whole-call evidence uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: wcag22-feasibility-wasm-boundary-${{ github.sha }}-attempt-${{ github.run_attempt }} path: | - packages/colors/bench/wcag22-feasibility-wasm-boundary-v4.json + packages/colors/bench/wcag22-feasibility-wasm-boundary-v5.json packages/colors/pkg/labcolors_bg.wasm packages/colors/compiler/labcolors_compiler_bg.wasm if-no-files-found: error diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b09d7c8..04854c23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,13 @@ Migration-note: [exact alpha / typed Glow](docs/migrations/exact-alpha-glow.md), ### Added +- #296-C3 публикует атомарную операцию в обоих адаптерах: compiler-WASM/npm + (`evaluateWcag22ExplicitSelection`, `wcag22ExplicitSelectionMaxBytes` в + `@labpics/colors/compiler` с тем же hostile-preflight, что у feasibility) и + UniFFI/Swift (`evaluate_wcag22_explicit_selection_raw_v1` + типизированная + Codable-проекция исхода). Оба адаптера повекторно реплеят conformance-family; + clean-consumer smoke упакованного пакета прогоняет обе операции. Runtime-роль + по-прежнему не резолвит protocol; топология фич проверяется CI-гейтом. - Атомарная операция `wcag22-explicit-selection-v1` (#296-C2): Core-функция `evaluate_and_select` исполняет `feasibility → полная валидация политики → selection → финальная перепроверка` за один вызов. Ошибка A-фазы приоритетна; @@ -33,17 +40,14 @@ Migration-note: [exact alpha / typed Glow](docs/migrations/exact-alpha-glow.md), кодирует реальный запрос ровно в это число байтов, строгий декодер с typed-ошибками для неизвестных schema/domain/profile/policy kind, фазовая атрибуция дефектов конструирования и исходы без `Deserialize`. - Ни один публикуемый adapter (compiler WASM, UniFFI/Swift, npm) не рекламирует - capability до #296-C3. + До #296-C3 capability не рекламировалась; C3 публикует её в обеих ролях. - Conformance pack 6.0.0 добавляет ровно одно семейство `wcag22-explicit-selection.json` (15 векторов: четыре законных терминала, opposite-order пара с байт-идентичным feasibility-поддеревом, policy-ошибки после каждого успешного A-терминала, приоритет A-ошибки, unsupported policy - kind, unicode-идентичности). Повекторный replay семейства владеет нативный - reference runner conformance-крейта; wasm32-паритет сверяет только счётчики - манифеста, а ни один публикуемый adapter семейство не читает и не - рекламирует. Прежние семь семейств byte-identical, npm/Swift реплеят - прежние семь. + kind, unicode-идентичности). Прежние семь семейств byte-identical. После + #296-C3 семейство воспроизводят нативный reference runner и оба публикуемых + адаптера (compiler WASM/npm и UniFFI/Swift) — побайтно. - Versioned `evaluate_wcag22_srgb8` / `evaluate_wcag22_hex` и эквивалентные WASM/TypeScript/UniFFI границы. Criterion всегда объявляет клиент; verdict возвращает точное terminal evidence без epsilon или округлённого ratio. diff --git a/bindings/swift/.gitignore b/bindings/swift/.gitignore index 24395834..166bb1a5 100644 --- a/bindings/swift/.gitignore +++ b/bindings/swift/.gitignore @@ -1,8 +1,9 @@ # Сгенерированные UniFFI-биндинги — производный артефакт, генерируется в CI -# (uniffi-bindgen) ПЕРЕД `swift test`. Единственный отслеживаемый Swift-source -# ниже — hand-written protocol consumer. +# (uniffi-bindgen) ПЕРЕД `swift test`. Отслеживаемые Swift-sources ниже — +# hand-written protocol consumers. Sources/LabColors/*.swift !Sources/LabColors/Wcag22FeasibilityProtocol.swift +!Sources/LabColors/Wcag22ExplicitSelectionProtocol.swift Sources/labcolorsFFI/*.h Sources/labcolorsFFI/module.modulemap Sources/labcolorsFFI/*.modulemap diff --git a/bindings/swift/Package.swift b/bindings/swift/Package.swift index 8f87fe9d..d677b23c 100644 --- a/bindings/swift/Package.swift +++ b/bindings/swift/Package.swift @@ -5,9 +5,10 @@ // Swift-контейнере на Linux x86_64; ручной macOS/arm64 path не является // достигнутой аттестацией. Файлы в `Sources/LabColors` и // `Sources/labcolorsFFI` генерирует uniffi-bindgen ПЕРЕД `swift test` (см. -// .github/workflows/native-conformance.yml). Единственный hand-written source -// в `Sources/LabColors` — exhaustive protocol wrapper -// `Wcag22FeasibilityProtocol.swift`; сгенерированное не коммитится. +// .github/workflows/native-conformance.yml). Hand-written sources в +// `Sources/LabColors` — exhaustive protocol wrappers +// `Wcag22FeasibilityProtocol.swift` и `Wcag22ExplicitSelectionProtocol.swift`; +// сгенерированное не коммитится. import PackageDescription let package = Package( diff --git a/bindings/swift/Sources/LabColors/Wcag22ExplicitSelectionProtocol.swift b/bindings/swift/Sources/LabColors/Wcag22ExplicitSelectionProtocol.swift new file mode 100644 index 00000000..4f8fb678 --- /dev/null +++ b/bindings/swift/Sources/LabColors/Wcag22ExplicitSelectionProtocol.swift @@ -0,0 +1,851 @@ +import Foundation + +/// Typed Swift projection of the atomic `wcag22-explicit-selection-v1` +/// operation (#296-C3). The wire truth is +/// `crates/labcolors-protocol/src/explicit_selection.rs`: one request carries +/// only the client-declared finite domain, relations, resource profile and +/// policy; every count, digest, matrix, evaluation ID, proof and receipt is +/// derived and sealed by Core in a single call. These types therefore decode +/// outcomes only — proof-bearing structs expose `let` fields without public +/// initializers, and `Wcag22ExplicitSelectionOutcomeV1.init(from:)` re-checks +/// the structural terminal laws before a value escapes the decoder. + +/// Stable Core key of the single V1 explicit domain kind. +public enum Wcag22ExplicitDomainKindV1: String, Codable, Sendable { + case explicitSrgb8SetV1 = "explicit-srgb8-set-v1" +} + +/// One canonical explicit candidate: opaque client identity plus exact final +/// encoded-sRGB8 bytes. +public struct Wcag22ExplicitCandidateV1: Codable, Equatable, Sendable { + public let candidateId: String + public let emitted: Srgb8BytesV1 +} + +/// Sealed fully projected proof of one complete explicit enumeration. +/// Domain-neutral descriptor: kind, digest and exact finite cardinality; the +/// neutral-only `domainFirst`/`domainLast` fields do not exist here, and the +/// feasible partition is variable-width (`ceil(candidates/8)` bytes, LSB0 by +/// canonical candidate index). +public struct Wcag22ExplicitSelectionEvaluationProofV1: Codable, Equatable, Sendable { + public let evaluationId: Bytes32V1 + public let resourceProfileId: Wcag22FeasibilityResourceProfileIdV1 + public let domainKind: Wcag22ExplicitDomainKindV1 + public let domainDigest: Bytes32V1 + public let candidateCount: DecimalU64V1 + public let relationSetDigest: Bytes32V1 + public let canonicalRelations: DecimalU64V1 + public let applicableRelations: DecimalU64V1 + public let notApplicableRelations: DecimalU64V1 + public let applicableEdges: DecimalU64V1 + public let logicalAssessments: DecimalU64V1 + public let matrixDigest: Bytes32V1 + public let partition: [UInt8] + public let wcag22ProfileId: Wcag22FeasibilityProfileIdV1 + public let artifactId: Wcag22FeasibilityArtifactIdV1 + public let boundId: Wcag22FeasibilityBoundIdV1 + public let proofId: Wcag22FeasibilityProofIdV1 + public let proofSha256: Bytes32V1 +} + +/// Complete evaluated payload shared by the `selected`, `noSelection` and +/// `infeasible` terminals. Candidates are transported once, in canonical +/// (byte-sorted by opaque ID) order. +public struct Wcag22ExplicitSelectionEvaluatedV1: Codable, Equatable, Sendable { + public let candidates: [Wcag22ExplicitCandidateV1] + public let relations: [Wcag22FeasibilityRelationV1] + public let failureMatrix: [UInt8] + public let proof: Wcag22ExplicitSelectionEvaluationProofV1 +} + +/// Declaration-only terminal without fabricated numerical evidence. +public struct Wcag22ExplicitSelectionNotEvaluatedV1: Codable, Equatable, Sendable { + public let domainKind: Wcag22ExplicitDomainKindV1 + public let domainDigest: Bytes32V1 + public let candidateCount: DecimalU64V1 + public let relationSetDigest: Bytes32V1 + public let resourceProfileId: Wcag22FeasibilityResourceProfileIdV1 + public let candidates: [Wcag22ExplicitCandidateV1] + public let relations: [Wcag22FeasibilityRelationV1] +} + +/// Sealed final recheck of the selected row against the same numerical proof. +public struct Wcag22ExplicitSelectionFinalVerificationV1: Codable, Equatable, Sendable { + public let relationSetDigest: Bytes32V1 + public let verifiedApplicableEdges: DecimalU64V1 + public let wcag22ProfileId: Wcag22FeasibilityProfileIdV1 + public let artifactId: Wcag22FeasibilityArtifactIdV1 + public let boundId: Wcag22FeasibilityBoundIdV1 + public let proofId: Wcag22FeasibilityProofIdV1 + public let proofSha256: Bytes32V1 + public let receiptDigest: Bytes32V1 +} + +/// Sealed selected candidate with its final receipt. +public struct Wcag22ExplicitSelectionSelectedV1: Codable, Equatable, Sendable { + public let candidateId: String + public let emitted: Srgb8BytesV1 + public let evaluationId: Bytes32V1 + public let policyId: String + public let policyDigest: Bytes32V1 + public let selectedPolicyOrdinal: DecimalU64V1 + public let receiptDigest: Bytes32V1 + public let finalVerification: Wcag22ExplicitSelectionFinalVerificationV1 +} + +/// Exhaustive reason for the absence of a selection. +public enum Wcag22ExplicitSelectionNoSelectionReasonV1: String, Codable, Sendable { + case noDeclaredCandidateFeasible +} + +/// Sealed refusal of a valid policy without a hidden fallback. +public struct Wcag22ExplicitSelectionNoSelectionV1: Codable, Equatable, Sendable { + public let reason: Wcag22ExplicitSelectionNoSelectionReasonV1 + public let policyId: String + public let policyDigest: Bytes32V1 + public let evaluationId: Bytes32V1 +} + +/// Sealed binding of a fully validated policy without a selection receipt. +public struct Wcag22ExplicitSelectionPolicyBindingV1: Codable, Equatable, Sendable { + public let policyId: String + public let policyDigest: Bytes32V1 + public let declaredEntries: DecimalU64V1 +} + +/// Successful result: exactly one of the four lawful terminals. The enum shape +/// itself guarantees that `infeasible`/`notEvaluated` cannot carry a selection +/// and that `selected`/`noSelection` cannot drop their evaluated evidence. +public enum Wcag22ExplicitSelectionResultV1: Codable, Equatable, Sendable { + case selected( + feasibility: Wcag22ExplicitSelectionEvaluatedV1, + selection: Wcag22ExplicitSelectionSelectedV1 + ) + case noSelection( + feasibility: Wcag22ExplicitSelectionEvaluatedV1, + selection: Wcag22ExplicitSelectionNoSelectionV1 + ) + case infeasible( + feasibility: Wcag22ExplicitSelectionEvaluatedV1, + policy: Wcag22ExplicitSelectionPolicyBindingV1 + ) + case notEvaluated( + feasibility: Wcag22ExplicitSelectionNotEvaluatedV1, + policy: Wcag22ExplicitSelectionPolicyBindingV1 + ) + + private enum CodingKeys: String, CodingKey { case status, feasibility, selection, policy } + private enum Status: String, Codable { case selected, noSelection, infeasible, notEvaluated } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch try container.decode(Status.self, forKey: .status) { + case .selected: + self = try .selected( + feasibility: container.decode( + Wcag22ExplicitSelectionEvaluatedV1.self, forKey: .feasibility), + selection: container.decode( + Wcag22ExplicitSelectionSelectedV1.self, forKey: .selection)) + case .noSelection: + self = try .noSelection( + feasibility: container.decode( + Wcag22ExplicitSelectionEvaluatedV1.self, forKey: .feasibility), + selection: container.decode( + Wcag22ExplicitSelectionNoSelectionV1.self, forKey: .selection)) + case .infeasible: + self = try .infeasible( + feasibility: container.decode( + Wcag22ExplicitSelectionEvaluatedV1.self, forKey: .feasibility), + policy: container.decode( + Wcag22ExplicitSelectionPolicyBindingV1.self, forKey: .policy)) + case .notEvaluated: + self = try .notEvaluated( + feasibility: container.decode( + Wcag22ExplicitSelectionNotEvaluatedV1.self, forKey: .feasibility), + policy: container.decode( + Wcag22ExplicitSelectionPolicyBindingV1.self, forKey: .policy)) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .selected(feasibility, selection): + try container.encode(Status.selected, forKey: .status) + try container.encode(feasibility, forKey: .feasibility) + try container.encode(selection, forKey: .selection) + case let .noSelection(feasibility, selection): + try container.encode(Status.noSelection, forKey: .status) + try container.encode(feasibility, forKey: .feasibility) + try container.encode(selection, forKey: .selection) + case let .infeasible(feasibility, policy): + try container.encode(Status.infeasible, forKey: .status) + try container.encode(feasibility, forKey: .feasibility) + try container.encode(policy, forKey: .policy) + case let .notEvaluated(feasibility, policy): + try container.encode(Status.notEvaluated, forKey: .status) + try container.encode(feasibility, forKey: .feasibility) + try container.encode(policy, forKey: .policy) + } + } + + fileprivate func validate(codingPath: [CodingKey]) throws { + func corrupted(_ message: String) -> DecodingError { + .dataCorrupted(.init(codingPath: codingPath, debugDescription: message)) + } + func utf8(_ id: String) -> [UInt8] { Array(id.utf8) } + func bitBytes(_ bits: Int) -> Int { bits / 8 + (bits % 8 == 0 ? 0 : 1) } + func lsb0(_ bytes: [UInt8], _ index: Int) -> Bool { + (bytes[index / 8] & (UInt8(1) << UInt8(index % 8))) != 0 + } + func relationCounts( + _ relations: [Wcag22FeasibilityRelationV1] + ) throws -> (applicable: Int, notApplicable: Int, edges: Int) { + var applicable = 0 + var notApplicable = 0 + var edges = 0 + for relation in relations { + switch relation { + case let .applicable(_, _, _, adjacent): + applicable += 1 + let (next, overflow) = edges.addingReportingOverflow(adjacent.count) + guard !overflow else { throw corrupted("applicable edge count overflow") } + edges = next + case .notApplicable: + notApplicable += 1 + } + } + return (applicable, notApplicable, edges) + } + /// Canonical relation order mirrors Core: relations strictly + /// increasing by raw relation-ID bytes, each applicable adjacency + /// strictly increasing and unique. The transported relationSetDigest + /// was computed by Core over exactly this order, so a reordered wire + /// would silently disagree with its own sealed digest. + func validateCanonicalRelations(_ relations: [Wcag22FeasibilityRelationV1]) throws { + func relationId(_ relation: Wcag22FeasibilityRelationV1) -> String { + switch relation { + case let .applicable(id, _, _, _): return id + case let .notApplicable(id, _, _): return id + } + } + for pair in zip(relations, relations.dropFirst()) { + guard utf8(relationId(pair.0)) + .lexicographicallyPrecedes(utf8(relationId(pair.1))) + else { + throw corrupted("relations differ from canonical byte-sorted ID order") + } + } + for relation in relations { + guard case let .applicable(_, _, _, adjacent) = relation else { continue } + for pair in zip(adjacent, adjacent.dropFirst()) { + let left = [pair.0.red, pair.0.green, pair.0.blue] + let right = [pair.1.red, pair.1.green, pair.1.blue] + guard left.lexicographicallyPrecedes(right) else { + throw corrupted( + "applicable adjacency differs from canonical sorted unique order") + } + } + } + } + /// Canonical explicit order: opaque IDs strictly increasing by raw + /// UTF-8 bytes (which also proves uniqueness). Swift `String` equality + /// is Unicode-canonical, so the opaque byte law is checked on bytes. + func validateCanonicalCandidates(_ candidates: [Wcag22ExplicitCandidateV1]) throws { + guard !candidates.isEmpty else { + throw corrupted("explicit domain must transport at least one candidate") + } + for pair in zip(candidates, candidates.dropFirst()) { + guard utf8(pair.0.candidateId) + .lexicographicallyPrecedes(utf8(pair.1.candidateId)) + else { + throw corrupted("candidates differ from canonical byte-sorted ID order") + } + } + } + func validateEvaluated( + _ value: Wcag22ExplicitSelectionEvaluatedV1, + expectsFeasible: Bool + ) throws { + try validateCanonicalCandidates(value.candidates) + try validateCanonicalRelations(value.relations) + let candidateCount = value.candidates.count + let counts = try relationCounts(value.relations) + guard counts.applicable > 0, counts.edges > 0 else { + throw corrupted("evaluated terminal must contain an applicable edge") + } + let (assessments, assessmentsOverflow) = + candidateCount.multipliedReportingOverflow(by: counts.edges) + guard !assessmentsOverflow else { + throw corrupted("logical assessment count overflow") + } + guard value.failureMatrix.count == bitBytes(assessments) else { + throw corrupted("failure matrix length does not equal ceil(C*E/8)") + } + guard value.proof.partition.count == bitBytes(candidateCount) else { + throw corrupted("partition length does not equal ceil(C/8)") + } + guard value.proof.candidateCount.value == UInt64(candidateCount), + value.proof.canonicalRelations.value == UInt64(value.relations.count), + value.proof.applicableRelations.value == UInt64(counts.applicable), + value.proof.notApplicableRelations.value == UInt64(counts.notApplicable), + value.proof.applicableEdges.value == UInt64(counts.edges), + value.proof.logicalAssessments.value == UInt64(assessments) + else { + throw corrupted("proof counts do not match transported candidates/relations") + } + + var hasFeasibleCandidate = false + for candidate in 0.. UInt64 + let evaluateRaw: (Data) throws -> Data + let envelopeTooLarge: (UInt64) throws -> Data + + static let live = Self( + maxRequestBytes: wcag22ExplicitSelectionMaxRequestBytesV1, + evaluateRaw: { try evaluateWcag22ExplicitSelectionRawV1(request: $0) }, + envelopeTooLarge: { + try wcag22ExplicitSelectionEnvelopeTooLargeV1(requestedBytes: $0) + }) +} + +/// Exact protocol-owned request byte ceiling. +public func wcag22ExplicitSelectionMaxBytes() -> UInt64 { + Wcag22ExplicitSelectionBridge.live.maxRequestBytes() +} + +/// Evaluate one exact atomic V1 request carried as `Data`. +public func evaluateWcag22ExplicitSelection( + _ request: Data +) throws -> Wcag22ExplicitSelectionOutcomeV1 { + try evaluateWcag22ExplicitSelection(request, using: .live) +} + +/// Evaluate one exact atomic V1 request carried as bytes. The size comparison +/// occurs before `Data` materialization and therefore before the raw UniFFI +/// copy. +public func evaluateWcag22ExplicitSelection( + _ request: [UInt8] +) throws -> Wcag22ExplicitSelectionOutcomeV1 { + let bridge = Wcag22ExplicitSelectionBridge.live + return try evaluateWcag22ExplicitSelection( + requestedBytes: UInt64(request.count), + using: bridge, + evaluateAdmitted: { try bridge.evaluateRaw(Data(request)) }) +} + +func evaluateWcag22ExplicitSelection( + _ request: Data, + using bridge: Wcag22ExplicitSelectionBridge +) throws -> Wcag22ExplicitSelectionOutcomeV1 { + try evaluateWcag22ExplicitSelection( + requestedBytes: UInt64(request.count), + using: bridge, + evaluateAdmitted: { try bridge.evaluateRaw(request) }) +} + +private func evaluateWcag22ExplicitSelection( + requestedBytes: UInt64, + using bridge: Wcag22ExplicitSelectionBridge, + evaluateAdmitted: () throws -> Data +) throws -> Wcag22ExplicitSelectionOutcomeV1 { + let encoded: Data + if requestedBytes > bridge.maxRequestBytes() { + encoded = try bridge.envelopeTooLarge(requestedBytes) + } else { + encoded = try evaluateAdmitted() + } + return try JSONDecoder().decode(Wcag22ExplicitSelectionOutcomeV1.self, from: encoded) +} diff --git a/bindings/swift/Sources/LabColors/Wcag22FeasibilityProtocol.swift b/bindings/swift/Sources/LabColors/Wcag22FeasibilityProtocol.swift index c1c4e917..482515eb 100644 --- a/bindings/swift/Sources/LabColors/Wcag22FeasibilityProtocol.swift +++ b/bindings/swift/Sources/LabColors/Wcag22FeasibilityProtocol.swift @@ -136,19 +136,22 @@ public enum Wcag22FeasibilityRelationV1: Codable, Equatable, Sendable { } } + /// Key order mirrors the canonical Rust `RelationV1` serializer + /// (`relationId`, `occurrenceId`, `kind`, …) so an order-preserving + /// re-encode reproduces the exact wire bytes. public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case let .applicable(relationId, occurrenceId, criterion, adjacent): - try container.encode(Kind.applicable, forKey: .kind) try container.encode(relationId, forKey: .relationId) try container.encode(occurrenceId, forKey: .occurrenceId) + try container.encode(Kind.applicable, forKey: .kind) try container.encode(criterion, forKey: .criterion) try container.encode(adjacent, forKey: .adjacent) case let .notApplicable(relationId, occurrenceId, reasonId): - try container.encode(Kind.notApplicable, forKey: .kind) try container.encode(relationId, forKey: .relationId) try container.encode(occurrenceId, forKey: .occurrenceId) + try container.encode(Kind.notApplicable, forKey: .kind) try container.encode(reasonId, forKey: .reasonId) } } diff --git a/bindings/swift/Tests/LabColorsConformanceTests/ConformanceTests.swift b/bindings/swift/Tests/LabColorsConformanceTests/ConformanceTests.swift index 7a146527..83ebd5fd 100644 --- a/bindings/swift/Tests/LabColorsConformanceTests/ConformanceTests.swift +++ b/bindings/swift/Tests/LabColorsConformanceTests/ConformanceTests.swift @@ -1024,6 +1024,258 @@ final class ConformanceTests: XCTestCase { "raw_calls=\(rawCalls) scalar_calls=\(scalarCalls)") } } + + // MARK: - Atomic WCAG 2.2 explicit-selection protocol (#296-C3) + + func loadExplicitSelectionVectors() throws -> [Wcag22ExplicitSelectionVector] { + let vectors = try load( + "wcag22-explicit-selection.json", as: [Wcag22ExplicitSelectionVector].self) + XCTAssertEqual(vectors.count, 15, "atomic explicit-selection family cardinality") + return vectors + } + + /// (a) Реплей всех 15 векторов: байт-точная сверка сырого FFI-выхода с + /// закоммиченным исходом плюс равенство типизированной проекции. + func testWcag22ExplicitSelectionPackReplaysThroughRawAndTypedSwiftSurfaces() throws { + for vector in try loadExplicitSelectionVectors() { + let request = Data(vector.requestJson.utf8) + let raw = try evaluateWcag22ExplicitSelectionRawV1(request: request) + XCTAssertEqual( + raw, Data(vector.outcomeJson.utf8), + "canonical FFI bytes \(vector.caseId)") + + let typed = try evaluateWcag22ExplicitSelection(request) + let expected = try JSONDecoder().decode( + Wcag22ExplicitSelectionOutcomeV1.self, from: Data(vector.outcomeJson.utf8)) + XCTAssertEqual(typed, expected, "typed outcome \(vector.caseId)") + } + } + + /// (b) decode → re-encode через Codable-типы воспроизводит канонический + /// JSON-терм: ни одно поле не теряется, не переименовывается и не меняет + /// представления. Порядок ключей serde пинуется байт-точно сырым + /// FFI-реплеем выше; Swift-овский JSONEncoder порядок вставки не + /// сохраняет, поэтому байтовая сверка идёт после нормализации ОБЕИХ + /// сторон одним и тем же sorted-keys сериализатором. + func testWcag22ExplicitSelectionTypedDecodeReencodesCanonicalTree() throws { + func normalized(_ data: Data) throws -> Data { + try JSONSerialization.data( + withJSONObject: JSONSerialization.jsonObject(with: data), + options: [.sortedKeys]) + } + for vector in try loadExplicitSelectionVectors() { + let canonical = Data(vector.outcomeJson.utf8) + let decoded = try JSONDecoder().decode( + Wcag22ExplicitSelectionOutcomeV1.self, from: canonical) + let reencoded = try JSONEncoder().encode(decoded) + let reencodedTerm = try normalized(reencoded) + let canonicalTerm = try normalized(canonical) + XCTAssertEqual( + reencodedTerm, canonicalTerm, + "decode→re-encode drifted from the canonical term \(vector.caseId)") + let redecoded = try JSONDecoder().decode( + Wcag22ExplicitSelectionOutcomeV1.self, from: reencoded) + XCTAssertEqual( + redecoded, decoded, + "re-encoded bytes must decode back to the same value \(vector.caseId)") + } + } + + /// (c) limit+1 отклоняется ДО сырой FFI-копии; typed-путь и скалярный + /// helper обязаны выдавать один и тот же канонический отказ. + func testWcag22ExplicitSelectionOversizePreflightMatchesScalarHelper() throws { + let maxBytes = wcag22ExplicitSelectionMaxBytes() + XCTAssertEqual(maxBytes, wcag22ExplicitSelectionMaxRequestBytesV1()) + // Кросс-платформенный пин конверта, разделяемый с packages/colors. + XCTAssertEqual(maxBytes, 3_889_322) + + let requestedBytes = maxBytes + 1 + var rawCalls = 0 + var scalarCalls = 0 + var ffiSubmittedBytes = 0 + let live = Wcag22ExplicitSelectionBridge.live + let observing = Wcag22ExplicitSelectionBridge( + maxRequestBytes: live.maxRequestBytes, + evaluateRaw: { request in + rawCalls += 1 + ffiSubmittedBytes += request.count + return try live.evaluateRaw(request) + }, + envelopeTooLarge: { requested in + scalarCalls += 1 + return try live.envelopeTooLarge(requested) + }) + let oversized = Data(repeating: 0x20, count: Int(requestedBytes)) + let outcome = try evaluateWcag22ExplicitSelection(oversized, using: observing) + XCTAssertEqual(rawCalls, 0, "limit+1 must not materialize/pass the raw FFI vector") + XCTAssertEqual(scalarCalls, 1) + XCTAssertEqual(ffiSubmittedBytes, 0) + guard case let .failure(.transport(.common(.envelopeTooLarge(requested, limit)))) = + outcome + else { + return XCTFail("oversize preflight did not preserve typed protocol failure") + } + XCTAssertEqual(requested.value, requestedBytes) + XCTAssertEqual(limit.value, maxBytes) + + let scalarBytes = try wcag22ExplicitSelectionEnvelopeTooLargeV1( + requestedBytes: requestedBytes) + let scalarOutcome = try JSONDecoder().decode( + Wcag22ExplicitSelectionOutcomeV1.self, from: scalarBytes) + XCTAssertEqual( + scalarOutcome, outcome, + "scalar helper and typed preflight must agree on the canonical failure") + } + + /// (d) Структурные мутации законного `selected`-терминала обязаны падать + /// на decode: испорченная ширина партиции, чужой status, потерянный + /// selection, перевёрнутый бит матрицы, недосчитанная финальная + /// перепроверка и нарушенный канонический порядок кандидатов. + func testWcag22ExplicitSelectionTypedConsumerRejectsStructuralMutations() throws { + let vectors = try loadExplicitSelectionVectors() + let fixture = try XCTUnwrap( + vectors.first { $0.caseId == "selected-declared-order-overrides-canonical" }) + let canonicalData = Data(fixture.outcomeJson.utf8) + XCTAssertNoThrow( + try JSONDecoder().decode(Wcag22ExplicitSelectionOutcomeV1.self, from: canonicalData), + "mutation oracle requires a lawful canonical fixture") + + func mutated( + _ change: (inout [String: Any]) throws -> Void + ) throws -> Data { + var root = try XCTUnwrap( + JSONSerialization.jsonObject(with: canonicalData) as? [String: Any]) + var result = try XCTUnwrap(root["result"] as? [String: Any]) + try change(&result) + root["result"] = result + return try JSONSerialization.data(withJSONObject: root, options: [.sortedKeys]) + } + func mutatedFeasibility( + _ change: (inout [String: Any]) throws -> Void + ) throws -> Data { + try mutated { result in + var feasibility = try XCTUnwrap(result["feasibility"] as? [String: Any]) + try change(&feasibility) + result["feasibility"] = feasibility + } + } + func assertDecodeFails(_ data: Data, _ context: String) { + XCTAssertThrowsError( + try JSONDecoder().decode(Wcag22ExplicitSelectionOutcomeV1.self, from: data), + context) + } + + // Лишний нулевой байт держит padding законным, но ломает ceil(C/8). + let widePartition = try mutatedFeasibility { feasibility in + var proof = try XCTUnwrap(feasibility["proof"] as? [String: Any]) + var partition = try XCTUnwrap(proof["partition"] as? [Int]) + partition.append(0) + proof["partition"] = partition + feasibility["proof"] = proof + } + assertDecodeFails(widePartition, "damaged partition width must fail decode") + + // Чужой терминальный ключ нейтральной feasibility-операции. + let foreignStatus = try mutated { $0["status"] = "feasible" } + assertDecodeFails(foreignStatus, "foreign status key must fail decode") + + // Selected без своего sealed-выбора непредставим. + let lostSelection = try mutated { $0.removeValue(forKey: "selection") } + assertDecodeFails(lostSelection, "lost selection on selected must fail decode") + + // Первый бит матрицы рассогласовывает партицию с candidate-major LSB0. + let flippedMatrixBit = try mutatedFeasibility { feasibility in + var matrix = try XCTUnwrap(feasibility["failureMatrix"] as? [Int]) + matrix[0] ^= 1 + feasibility["failureMatrix"] = matrix + } + assertDecodeFails(flippedMatrixBit, "flipped matrix bit must fail decode") + + // Финальная перепроверка обязана покрыть каждое applicable-ребро. + let partialVerification = try mutated { result in + var selection = try XCTUnwrap(result["selection"] as? [String: Any]) + var verification = try XCTUnwrap( + selection["finalVerification"] as? [String: Any]) + verification["verifiedApplicableEdges"] = "0" + selection["finalVerification"] = verification + result["selection"] = selection + } + assertDecodeFails( + partialVerification, "under-counted final verification must fail decode") + + // Канонический byte-sorted порядок кандидатов Core-owned. + let transposedCandidates = try mutatedFeasibility { feasibility in + var candidates = try XCTUnwrap(feasibility["candidates"] as? [[String: Any]]) + candidates.swapAt(0, 1) + feasibility["candidates"] = candidates + } + assertDecodeFails(transposedCandidates, "transposed candidates must fail decode") + } + + /// Полная алгебра отказов атомарной операции обязана пережить + /// encode→decode без потери ветки или payload. + func testWcag22ExplicitSelectionFailureAlgebraRoundTripsExhaustively() throws { + let one = DecimalU64V1(1) + let two = DecimalU64V1(2) + let rgb = Srgb8BytesV1(red: 1, green: 2, blue: 3) + let transport: [Wcag22ExplicitSelectionTransportErrorV1] = [ + .common(.envelopeTooLarge(requestedBytes: two, limitBytes: one)), + .common(.malformedEnvelope(.shape)), + .common(.unsupportedDomainId("future")), + .unsupportedPolicyKind("best-feasible-v1"), + ] + let feasibility: [Wcag22ExplicitSelectionFeasibilityErrorV1] = [ + .emptyCandidateId, + .emptyCandidates, + .duplicateCandidateId(candidateId: "twin"), + .common(.invalidRequest(.emptyRelations)), + .common(.resourceLimitExceeded( + profileId: .compileV1, + dimension: .rawAdjacentEntries, + requested: two, + limit: one)), + ] + let invalid: [Wcag22ExplicitSelectionInvalidRequestV1] = [ + .emptyPolicyId, + .emptyCandidateOrder, + .emptyCandidateId, + .arithmeticOverflow, + .policyCardinalityExceedsDomain(requested: two, domain: one), + .foreignCandidateId(candidateId: "foreign"), + .duplicateCandidateId(candidateId: "twin"), + ] + let integrity: [Wcag22ExplicitSelectionIntegrityViolationV1] = [ + .evaluatorContract( + candidateId: "c", relationId: "r", adjacent: rgb, violation: .inputMismatch), + .sealedDecisionMismatch( + candidateId: "c", relationId: "r", adjacent: rgb, + sealed: .pass, rechecked: .fail), + .selectedRowNotPassing(candidateId: "c", relationId: "r", adjacent: rgb), + .applicableEdgeCountMismatch(expected: one, observed: two), + .sealedTraversalArithmeticOverflow, + ] + var selection: [Wcag22ExplicitSelectionErrorV1] = invalid.map { .invalidRequest($0) } + selection.append(.resourceLimitExceeded( + profileId: .compileV1, + dimension: .logicalAssessments, + requested: two, + limit: one)) + selection += integrity.map { .integrityViolation($0) } + + let errors: [Wcag22ExplicitSelectionOperationErrorV1] = + transport.map { .transport($0) } + + feasibility.map { .feasibility($0) } + + selection.map { .selection($0) } + + [.incompatibleCoreContract] + XCTAssertEqual(errors.count, 23, "new branch requires an explicit test fixture") + for error in errors { + let outcome = Wcag22ExplicitSelectionOutcomeV1.failure(error) + let encoded = try JSONEncoder().encode(outcome) + let decoded = try JSONDecoder().decode( + Wcag22ExplicitSelectionOutcomeV1.self, from: encoded) + XCTAssertEqual(decoded, outcome) + } + } } // MARK: - Codable-зеркала схемы векторов @@ -1088,6 +1340,12 @@ struct Wcag22FeasibilityVector: Codable { let outcomeJson: String } +struct Wcag22ExplicitSelectionVector: Codable { + let caseId: String + let requestJson: String + let outcomeJson: String +} + struct FeasibilityBenchmarkContract: Codable { let sampleCount: Int let scenarios: [Scenario] diff --git a/conformance/README.md b/conformance/README.md index 85affeee..5bc87eef 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -52,7 +52,7 @@ | `muddiness.json` | замороженная legacy-координата `muddiness` | `{hex, score}` | | `wcag22.json` | финальная sRGB8-пара и явно выбранный критерий WCAG 2.2 | `{foreground, background, criterion, decision, *Q55, evidence*}` | | `wcag22-feasibility.json` | bounded request и полный compiler outcome | `{caseId, requestJson, outcomeJson}` | -| `wcag22-explicit-selection.json` | атомарная операция `feasibility → policy → selection → recheck`; replay владеет conformance-крейт | `{caseId, requestJson, outcomeJson}` | +| `wcag22-explicit-selection.json` | атомарная операция `feasibility → policy → selection → recheck`; воспроизводится нативно и обоими адаптерами | `{caseId, requestJson, outcomeJson}` | | `manifest.json` | метаданные и capability manifest численных решений | `{packVersion, coreVersion, packDigest, counts, numericalCapabilities}` | `wcag22-feasibility.json` не определяет собственную transport-схему. Поля diff --git a/crates/labcolors-compiler/Cargo.toml b/crates/labcolors-compiler/Cargo.toml index f325db78..95e25a75 100644 --- a/crates/labcolors-compiler/Cargo.toml +++ b/crates/labcolors-compiler/Cargo.toml @@ -13,7 +13,9 @@ description = "Offline WASM compiler operations for @labpics/colors/compiler." crate-type = ["cdylib", "rlib"] [dependencies] -labcolors-protocol = { path = "../labcolors-protocol" } +# #296-C3: compiler-роль публикует обе offline-операции — complete feasibility +# и атомарную explicit-selection. Runtime-роль по-прежнему не резолвит protocol. +labcolors-protocol = { path = "../labcolors-protocol", features = ["wcag22-explicit-selection"] } wasm-bindgen = { workspace = true } js-sys = "0.3" diff --git a/crates/labcolors-compiler/src/lib.rs b/crates/labcolors-compiler/src/lib.rs index d097ecc6..cfb5d022 100644 --- a/crates/labcolors-compiler/src/lib.rs +++ b/crates/labcolors-compiler/src/lib.rs @@ -273,3 +273,293 @@ fn protocol_outcome_to_js( js_sys::JSON::parse(json).map_err(|_| JsError::new("protocol JSON did not parse"))?; Ok(parsed.unchecked_into()) } + +// ───────────────────────────────────────────────────────────────────────────── +// Атомарная операция `wcag22-explicit-selection-v1` (#296-C3) +// ───────────────────────────────────────────────────────────────────────────── + +#[wasm_bindgen(typescript_custom_section)] +const TS_EXPLICIT_SELECTION_TYPES: &'static str = r##" +/** One explicit client-declared candidate. */ +export interface Wcag22ExplicitCandidateV1 { + readonly candidateId: string; + readonly emitted: Srgb8BytesV1; +} + +/** The sole V1 selection policy: first feasible ID in declared order. */ +export interface Wcag22ExplicitSelectionPolicyV1 { + readonly policyKind: "first-feasible-in-declared-order-v1"; + readonly policyId: string; + readonly orderedCandidateIds: ReadonlyArray; +} + +/** Strict decoded form of the UTF-8 JSON accepted by the byte API. */ +export interface Wcag22ExplicitSelectionRequestV1 { + readonly schemaVersion: 1; + readonly domainId: "explicit-srgb8-set-v1"; + readonly resourceProfileId: "compile-v1"; + readonly candidates: ReadonlyArray; + readonly relations: ReadonlyArray; + readonly policy: Wcag22ExplicitSelectionPolicyV1; +} + +/** Domain-neutral explicit-set proof: no neutral-only first/last fields. */ +export interface Wcag22ExplicitEvaluationProofV1 { + readonly evaluationId: Bytes32V1; + readonly resourceProfileId: "compile-v1"; + readonly domainKind: "explicit-srgb8-set-v1"; + readonly domainDigest: Bytes32V1; + readonly candidateCount: DecimalU64V1; + readonly relationSetDigest: Bytes32V1; + readonly canonicalRelations: DecimalU64V1; + readonly applicableRelations: DecimalU64V1; + readonly notApplicableRelations: DecimalU64V1; + readonly applicableEdges: DecimalU64V1; + readonly logicalAssessments: DecimalU64V1; + readonly matrixDigest: Bytes32V1; + /** Variable-width feasible partition, candidate-index LSB0. */ + readonly partition: ReadonlyArray; + readonly wcag22ProfileId: "wcag22-srgb8-contrast-v1"; + readonly artifactId: "wcag22-srgb8-luminance-q55-v1"; + readonly boundId: "wcag22-srgb8-outward-q55-v1"; + readonly proofId: "wcag22-srgb8-full-domain-q55-v1"; + readonly proofSha256: Bytes32V1; +} + +export interface Wcag22ExplicitEvaluatedV1 { + /** Canonical candidates in exact ID-byte order, once. */ + readonly candidates: ReadonlyArray; + readonly relations: ReadonlyArray; + /** Candidate-major failure bits at `candidate * E + edge`, packed LSB0. */ + readonly failureMatrix: ReadonlyArray; + readonly proof: Wcag22ExplicitEvaluationProofV1; +} + +export interface Wcag22ExplicitNotEvaluatedV1 { + readonly domainKind: "explicit-srgb8-set-v1"; + readonly domainDigest: Bytes32V1; + readonly candidateCount: DecimalU64V1; + readonly relationSetDigest: Bytes32V1; + readonly resourceProfileId: "compile-v1"; + readonly candidates: ReadonlyArray; + readonly relations: ReadonlyArray; +} + +export interface Wcag22ExplicitFinalVerificationV1 { + readonly relationSetDigest: Bytes32V1; + readonly verifiedApplicableEdges: DecimalU64V1; + readonly wcag22ProfileId: "wcag22-srgb8-contrast-v1"; + readonly artifactId: "wcag22-srgb8-luminance-q55-v1"; + readonly boundId: "wcag22-srgb8-outward-q55-v1"; + readonly proofId: "wcag22-srgb8-full-domain-q55-v1"; + readonly proofSha256: Bytes32V1; + readonly receiptDigest: Bytes32V1; +} + +export interface Wcag22ExplicitSelectedV1 { + readonly candidateId: string; + readonly emitted: Srgb8BytesV1; + readonly evaluationId: Bytes32V1; + readonly policyId: string; + readonly policyDigest: Bytes32V1; + readonly selectedPolicyOrdinal: DecimalU64V1; + readonly receiptDigest: Bytes32V1; + readonly finalVerification: Wcag22ExplicitFinalVerificationV1; +} + +export interface Wcag22ExplicitNoSelectionV1 { + readonly reason: "noDeclaredCandidateFeasible"; + readonly policyId: string; + readonly policyDigest: Bytes32V1; + readonly evaluationId: Bytes32V1; +} + +export interface Wcag22ExplicitPolicyBindingV1 { + readonly policyId: string; + readonly policyDigest: Bytes32V1; + readonly declaredEntries: DecimalU64V1; +} + +export type Wcag22ExplicitSelectionResultV1 = + | { + readonly status: "selected"; + readonly feasibility: Wcag22ExplicitEvaluatedV1; + readonly selection: Wcag22ExplicitSelectedV1; + } + | { + readonly status: "noSelection"; + readonly feasibility: Wcag22ExplicitEvaluatedV1; + readonly selection: Wcag22ExplicitNoSelectionV1; + } + | { + readonly status: "infeasible"; + readonly feasibility: Wcag22ExplicitEvaluatedV1; + readonly policy: Wcag22ExplicitPolicyBindingV1; + } + | { + readonly status: "notEvaluated"; + readonly feasibility: Wcag22ExplicitNotEvaluatedV1; + readonly policy: Wcag22ExplicitPolicyBindingV1; + }; + +/** Explicit-domain invalid-request codes extend the shared feasibility set. */ +export type Wcag22ExplicitFeasibilityInvalidRequestV1 = + | Wcag22FeasibilityInvalidRequestV1 + | { readonly code: "emptyCandidateId" } + | { readonly code: "emptyCandidates" } + | { readonly code: "duplicateCandidateId"; readonly candidateId: string }; + +/** Feasibility-phase failures of the atomic operation. */ +export type Wcag22ExplicitFeasibilityCoreErrorV1 = + | Exclude + | { + readonly code: "invalidRequest"; + readonly details: Wcag22ExplicitFeasibilityInvalidRequestV1; + }; + +export type Wcag22ExplicitSelectionTransportErrorV1 = + | Wcag22FeasibilityTransportErrorV1 + | { readonly code: "unsupportedPolicyKind"; readonly received: string }; + +export type Wcag22ExplicitInvalidSelectionRequestV1 = + | { readonly code: "emptyPolicyId" } + | { readonly code: "emptyCandidateOrder" } + | { readonly code: "emptyCandidateId" } + | { readonly code: "arithmeticOverflow" } + | { + readonly code: "policyCardinalityExceedsDomain"; + readonly requested: DecimalU64V1; + readonly domain: DecimalU64V1; + } + | { readonly code: "foreignCandidateId"; readonly candidateId: string } + | { readonly code: "duplicateCandidateId"; readonly candidateId: string }; + +export type Wcag22ExplicitDecisionV1 = "pass" | "fail"; + +export type Wcag22ExplicitSelectionIntegrityViolationV1 = + | { + readonly code: "evaluatorContract"; + readonly candidateId: string; + readonly relationId: string; + readonly adjacent: Srgb8BytesV1; + readonly violation: Wcag22FeasibilityEvaluatorInvariantV1; + } + | { + readonly code: "sealedDecisionMismatch"; + readonly candidateId: string; + readonly relationId: string; + readonly adjacent: Srgb8BytesV1; + readonly sealed: Wcag22ExplicitDecisionV1; + readonly rechecked: Wcag22ExplicitDecisionV1; + } + | { + readonly code: "selectedRowNotPassing"; + readonly candidateId: string; + readonly relationId: string; + readonly adjacent: Srgb8BytesV1; + } + | { + readonly code: "applicableEdgeCountMismatch"; + readonly expected: DecimalU64V1; + readonly observed: DecimalU64V1; + } + | { readonly code: "sealedTraversalArithmeticOverflow" }; + +export type Wcag22ExplicitSelectionErrorV1 = + | { + readonly code: "invalidRequest"; + readonly details: Wcag22ExplicitInvalidSelectionRequestV1; + } + | { + readonly code: "resourceLimitExceeded"; + readonly details: { + readonly profileId: "compile-v1"; + readonly dimension: Wcag22FeasibilityResourceDimensionV1; + readonly requested: DecimalU64V1; + readonly limit: DecimalU64V1; + }; + } + | { + readonly code: "integrityViolation"; + readonly details: Wcag22ExplicitSelectionIntegrityViolationV1; + }; + +export type Wcag22ExplicitSelectionOperationErrorV1 = + | { + readonly source: "transport"; + readonly error: Wcag22ExplicitSelectionTransportErrorV1; + } + | { + readonly source: "feasibility"; + readonly error: Wcag22ExplicitFeasibilityCoreErrorV1; + } + | { readonly source: "selection"; readonly error: Wcag22ExplicitSelectionErrorV1 } + | { readonly source: "incompatibleCoreContract" }; + +export type Wcag22ExplicitSelectionOutcomeV1 = + | { + readonly schemaVersion: 1; + readonly outcome: "success"; + readonly result: Wcag22ExplicitSelectionResultV1; + } + | { + readonly schemaVersion: 1; + readonly outcome: "failure"; + readonly error: Wcag22ExplicitSelectionOperationErrorV1; + }; +"##; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Wcag22ExplicitSelectionOutcomeV1")] + pub type JsWcag22ExplicitSelectionOutcomeV1; +} + +const WASM_MAX_EXPLICIT_SELECTION_ENVELOPE_BYTES_V1: u32 = { + assert!( + labcolors_protocol::explicit_selection::MAX_EXPLICIT_SELECTION_ENVELOPE_BYTES_V1 + <= u32::MAX as u64 + ); + labcolors_protocol::explicit_selection::MAX_EXPLICIT_SELECTION_ENVELOPE_BYTES_V1 as u32 +}; + +/// Exact protocol-owned ceiling of the atomic operation as a JS `number`. +#[wasm_bindgen(js_name = wcag22ExplicitSelectionMaxRequestBytesV1)] +pub fn wcag22_explicit_selection_max_request_bytes_v1() -> u32 { + WASM_MAX_EXPLICIT_SELECTION_ENVELOPE_BYTES_V1 +} + +/// Evaluate one strict atomic `wcag22-explicit-selection-v1` byte envelope. +#[wasm_bindgen(js_name = evaluateWcag22ExplicitSelectionV1)] +pub fn evaluate_wcag22_explicit_selection_v1( + request: &[u8], +) -> Result { + explicit_selection_outcome_to_js( + labcolors_protocol::explicit_selection::evaluate_wcag22_explicit_selection_v1(request), + ) +} + +/// Construct the canonical oversize failure without copying a rejected input. +#[wasm_bindgen(js_name = wcag22ExplicitSelectionEnvelopeTooLargeV1)] +pub fn wcag22_explicit_selection_envelope_too_large_v1( + requested_bytes: u64, +) -> Result { + explicit_selection_outcome_to_js( + labcolors_protocol::explicit_selection::explicit_selection_envelope_too_large_outcome_v1( + requested_bytes, + ), + ) +} + +fn explicit_selection_outcome_to_js( + outcome: labcolors_protocol::explicit_selection::OutcomeV1, +) -> Result { + let encoded = + labcolors_protocol::explicit_selection::encode_explicit_selection_outcome_v1(&outcome) + .map_err(|error| JsError::new(&format!("protocol encoding failed: {error}")))?; + let json = std::str::from_utf8(&encoded) + .map_err(|_| JsError::new("protocol emitted non-UTF-8 JSON"))?; + let parsed = + js_sys::JSON::parse(json).map_err(|_| JsError::new("protocol JSON did not parse"))?; + Ok(parsed.unchecked_into()) +} diff --git a/crates/labcolors-compiler/tests/wasm_parity.rs b/crates/labcolors-compiler/tests/wasm_parity.rs index 069cabe7..ec60e953 100644 --- a/crates/labcolors-compiler/tests/wasm_parity.rs +++ b/crates/labcolors-compiler/tests/wasm_parity.rs @@ -1,7 +1,9 @@ #![cfg(target_arch = "wasm32")] use labcolors_compiler::{ - evaluate_wcag22_feasibility_v1, wcag22_feasibility_envelope_too_large_v1, + evaluate_wcag22_explicit_selection_v1, evaluate_wcag22_feasibility_v1, + wcag22_explicit_selection_envelope_too_large_v1, + wcag22_explicit_selection_max_request_bytes_v1, wcag22_feasibility_envelope_too_large_v1, wcag22_feasibility_max_request_bytes_v1, }; use serde::Deserialize; @@ -59,3 +61,42 @@ fn compiler_rechecks_its_protocol_owned_envelope_ceiling() { .into(); assert_eq!(json_text(&raw), json_text(&scalar)); } + +#[wasm_bindgen_test] +fn compiler_replays_the_atomic_explicit_selection_family_byte_exactly() { + let vectors: Vec = serde_json::from_str(include_str!( + "../../../conformance/vectors/wcag22-explicit-selection.json" + )) + .expect("canonical family parses"); + assert!(!vectors.is_empty(), "anti-vacuum: family is non-empty"); + + for vector in vectors { + let outcome: JsValue = + evaluate_wcag22_explicit_selection_v1(vector.request_json.as_bytes()) + .expect("canonical projection") + .into(); + assert_eq!( + json_text(&outcome), + vector.outcome_json, + "{}", + vector.case_id + ); + } +} + +#[wasm_bindgen_test] +fn compiler_rechecks_the_atomic_protocol_owned_envelope_ceiling() { + let limit = wcag22_explicit_selection_max_request_bytes_v1(); + assert_eq!( + u64::from(limit), + labcolors_protocol::explicit_selection::MAX_EXPLICIT_SELECTION_ENVELOPE_BYTES_V1 + ); + + let raw: JsValue = evaluate_wcag22_explicit_selection_v1(&vec![b' '; limit as usize + 1]) + .expect("oversize failure is data") + .into(); + let scalar: JsValue = wcag22_explicit_selection_envelope_too_large_v1(u64::from(limit) + 1) + .expect("scalar oversize projection") + .into(); + assert_eq!(json_text(&raw), json_text(&scalar)); +} diff --git a/crates/labcolors-ffi/Cargo.toml b/crates/labcolors-ffi/Cargo.toml index 02138431..a81e0a50 100644 --- a/crates/labcolors-ffi/Cargo.toml +++ b/crates/labcolors-ffi/Cargo.toml @@ -28,7 +28,9 @@ path = "src/bin/uniffi_bindgen.rs" [dependencies] labcolors-core = { path = "../labcolors-core", default-features = false } -labcolors-protocol = { path = "../labcolors-protocol" } +# #296-C3: UniFFI-роль публикует обе offline-операции; explicit-selection +# включается той же non-default protocol-фичей, что и в compiler-роли. +labcolors-protocol = { path = "../labcolors-protocol", features = ["wcag22-explicit-selection"] } # Нативная FFI-обвязка. Библиотечная зависимость БЕЗ фичи cli (иначе clap течёт # в артефакт); cli включается только через passthrough-фичу ниже на генерации. uniffi = { version = "0.32", default-features = false } diff --git a/crates/labcolors-ffi/src/lib.rs b/crates/labcolors-ffi/src/lib.rs index c77a8d08..dba0ea17 100644 --- a/crates/labcolors-ffi/src/lib.rs +++ b/crates/labcolors-ffi/src/lib.rs @@ -485,6 +485,63 @@ pub fn wcag22_feasibility_envelope_too_large_v1( )) } +fn encode_explicit_selection_outcome( + outcome: labcolors_protocol::explicit_selection::OutcomeV1, +) -> Result, ColorError> { + labcolors_protocol::explicit_selection::encode_explicit_selection_outcome_v1(&outcome).map_err( + |error| ColorError::ProtocolEncodingFailed { + reason: error.to_string(), + }, + ) +} + +/// Exact protocol-owned ceiling of the atomic `wcag22-explicit-selection-v1` +/// request envelope. +#[uniffi::export] +#[must_use] +pub const fn wcag22_explicit_selection_max_request_bytes_v1() -> u64 { + labcolors_protocol::explicit_selection::MAX_EXPLICIT_SELECTION_ENVELOPE_BYTES_V1 +} + +/// Evaluate one exact atomic `wcag22-explicit-selection-v1` byte envelope and +/// return its canonical outcome bytes. +/// +/// Public-input, feasibility and selection failures are successful ABI calls +/// carrying `outcome:"failure"` data; only an internal canonical-encoding +/// failure returns [`ColorError::ProtocolEncodingFailed`]. +/// +/// # Errors +/// +/// [`ColorError::ProtocolEncodingFailed`] only if the sealed protocol outcome +/// cannot be serialized. +#[uniffi::export] +pub fn evaluate_wcag22_explicit_selection_raw_v1(request: Vec) -> Result, ColorError> { + encode_explicit_selection_outcome( + labcolors_protocol::explicit_selection::evaluate_wcag22_explicit_selection_v1(&request), + ) +} + +/// Construct canonical atomic oversize failure bytes from a scalar count. +/// +/// Host wrappers call this only after proving the request exceeds +/// [`wcag22_explicit_selection_max_request_bytes_v1`], so the rejected raw +/// buffer is never copied into UniFFI. +/// +/// # Errors +/// +/// [`ColorError::ProtocolEncodingFailed`] only if the sealed protocol outcome +/// cannot be serialized. +#[uniffi::export] +pub fn wcag22_explicit_selection_envelope_too_large_v1( + requested_bytes: u64, +) -> Result, ColorError> { + encode_explicit_selection_outcome( + labcolors_protocol::explicit_selection::explicit_selection_envelope_too_large_outcome_v1( + requested_bytes, + ), + ) +} + /// Контраст одного переднего плана на фоне под темой — `(Lc, WCAG)`. Дешёвый /// пер-кадровый примитив: один forward перцептивной модели для фона плюс один /// для переднего плана. @@ -977,6 +1034,39 @@ mod tests { ); } + #[test] + fn explicit_selection_boundary_exposes_the_protocol_byte_ceiling() { + assert_eq!( + wcag22_explicit_selection_max_request_bytes_v1(), + labcolors_protocol::explicit_selection::MAX_EXPLICIT_SELECTION_ENVELOPE_BYTES_V1 + ); + } + + #[test] + fn explicit_selection_boundary_is_a_mechanical_protocol_byte_shell() { + let request = br#"{"schemaVersion":1,"domainId":"explicit-srgb8-set-v1","resourceProfileId":"compile-v1","candidates":[{"candidateId":"only","emitted":[255,255,255]}],"relations":[{"relationId":"r","occurrenceId":"o","kind":"applicable","criterion":"sc-1.4.3-text-default","adjacent":[[0,0,0]]}],"policy":{"policyKind":"first-feasible-in-declared-order-v1","policyId":"client","orderedCandidateIds":["only"]}}"#; + let authoritative = + labcolors_protocol::explicit_selection::encode_explicit_selection_outcome_v1( + &labcolors_protocol::explicit_selection::evaluate_wcag22_explicit_selection_v1( + request, + ), + ) + .unwrap(); + assert_eq!( + evaluate_wcag22_explicit_selection_raw_v1(request.to_vec()).unwrap(), + authoritative + ); + } + + #[test] + fn explicit_selection_oversize_scalar_helper_matches_authoritative_raw_recheck() { + let requested = wcag22_explicit_selection_max_request_bytes_v1() + 1; + let oversized = vec![b' '; usize::try_from(requested).unwrap()]; + let from_scalar = wcag22_explicit_selection_envelope_too_large_v1(requested).unwrap(); + let from_raw = evaluate_wcag22_explicit_selection_raw_v1(oversized).unwrap(); + assert_eq!(from_scalar, from_raw); + } + #[test] fn wcag22_transport_preserves_core_decision_and_evidence() { let assessment = evaluate_wcag22( diff --git a/crates/labcolors-protocol/Cargo.toml b/crates/labcolors-protocol/Cargo.toml index a78df6e0..1f9b3176 100644 --- a/crates/labcolors-protocol/Cargo.toml +++ b/crates/labcolors-protocol/Cargo.toml @@ -15,10 +15,10 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" [features] -# Атомарная explicit-операция `wcag22-explicit-selection-v1` (#296-C2). -# Не входит в default: до #296-C3 ни один публикуемый adapter (compiler-WASM, -# UniFFI/Swift) не рекламирует capability; фичу включает только нативный -# conformance-генератор. Базовое core-ребро остаётся ровно wcag22-feasibility. +# Атомарная explicit-операция `wcag22-explicit-selection-v1` (#296-C2/C3). +# Не входит в default: каждую проекцию потребитель включает явно; после C3 её +# резолвят все три protocol-потребителя (compiler-WASM, UniFFI/Swift, +# conformance). Базовое core-ребро остаётся ровно wcag22-feasibility. wcag22-explicit-selection = ["labcolors-core/wcag22-explicit-feasibility"] [[test]] diff --git a/docs/verification-map.md b/docs/verification-map.md index ff2c76c4..273b437d 100644 --- a/docs/verification-map.md +++ b/docs/verification-map.md @@ -108,19 +108,19 @@ | только `Feasible` минтит неподделываемый источник выбора; клиентский список полностью проверяется до результата, а первый feasible-ID повторно проверяется тем же #284 evaluator ровно `E` раз; после создания source/policy публичный выбор не выделяет память; каждый применимый relation ID попадает в receipt один раз, а ordinals остаются глобальными для канонического графа | `wcag22_explicit_selection`: compile-fail sealing, exhaustive outcome match, opposite-order/property, invalid-tail/zero-call, `NoSelection`, exact-cell/all-edge fault, первый/последний fault второй applicable relation, exact-`E` и граничный byte-count тесты; отдельный counting-allocator target с положительным контролем; `verify_wcag22_explicit_selection_identity.py --self-test` | независимая Unicode-фикстура точной policy/receipt SHA-256-грамматики с ведущей canonical `NotApplicable` и двумя разными applicable relations; мутации каждого relation/edge-поля, applicable-only relation ordinals, сброса edge ordinal, пропуска/повтора/перестановки и канонизационные контроли | Строки транспорта ниже доказывают уже отгруженный вход зарегистрированной -нейтральной оси V1 и native-only атомарную explicit-операцию (#296-C2). -Explicit-набор имеет Protocol-проекцию `wcag22-explicit-selection-v1` за -non-default фичей; его WASM/FFI/npm/Swift проекции отсутствуют до #296-C3, и -эти evidence на них не расширяются. +нейтральной оси V1 и атомарную explicit-операцию `wcag22-explicit-selection-v1` +(#296-C2/C3): её Protocol-проекция живёт за одной non-default фичей, а +compiler-WASM/npm и UniFFI/Swift адаптеры публикуют её и повекторно реплеят +conformance-family (#296-C3). | transport V1 принимает только strict UTF-8 JSON bytes, сначала применяет выведенный предел envelope, затем сохраняет полную Core-алгебру как `Success(feasibility) \| Failure(error)` | `labcolors-protocol`: exact-limit witness, limit+1 decoder-spy, strict-schema/error-algebra tests и compile-fail запрет forged outcome | литеральная грамматика + Core resource-profile SSOT + тип-уровневый негативный контроль | | offline npm compiler проходит `labcolors-protocol → labcolors-compiler → @labpics/colors/compiler`, не повторяет Core-математику, preflight-ит intrinsic `Uint8Array.byteLength` до избегаемой ABI-копии и переносит domain/relations один раз; runtime dependency cone не содержит protocol | compiler `wasm_parity`; role-isolation dependency tests; hostile-view и feasibility boundary tests; `wcag22-feasibility.test.mjs` с независимым LSB0 reduction и mutation subject | дифференциальный compiler-boundary replay прежних семи семейств (pack 6) + независимый packed consumer | | UniFFI/Swift вызывает тот же protocol byte path, preflight-ит `Data`/`[UInt8]` до сырой FFI-копии и исчерпывающе декодирует terminal/error algebra | FFI mechanical-shell tests; Swift replay прежних семи семейств (pack 6), limit+1 bridge spy, structural mutation tests и extreme-shape whole-call observations | побайтный FFI/protocol differential + независимый Swift packed consumer | | атомарная операция `wcag22-explicit-selection-v1`: A-ошибка приоритетна, политика валидируется единственным SSOT после каждого успешного A-терминала, невыборные терминалы связывают политику без selection-receipt, терминал A перемещается без копирования и дополнительных аллокаций, а отказ не несёт частичного feasibility | `explicit/atomic.rs` unit + `wcag22_explicit_atomic` (дифференциал к A→B, идентичность классификации по трём терминалам, приоритет A, opposite-order, forge/mispair compile-fail) + `wcag22_explicit_atomic_alloc` counting-allocator differential | дифференциал против автономных A и B на байт-идентичных входах + позитивный allocation-контроль | | строгий explicit-транспорт: выведенный envelope точен и достижим, неизвестные schema/domain/profile/policy kind падают typed, дефекты конструирования атрибутируются своей фазе, сериализованный исход не десериализуется обратно в authority | `labcolors-protocol` explicit-selection unit seam (limit/limit+1 decoder-spy) + integration tests (достижимость MAX ровно в байтах, strict shapes, фазовая атрибуция, byte-identical feasibility-поддерево) + compile-fail Deserialize-запреты | машинно-проверяемый корнер-анализ const-assert + реальный запрос ровно в MAX байтов + Core-классификация вместо транспорта | -| conformance pack 6 добавляет только atomic explicit-selection family (native-only replay); прежние семь family остаются byte-identical, а 15 новых outcomes воспроизводятся canonical protocol encoder побайтно | `pack_v6_contract`; `reference_runner::protocol_reproduces_committed_wcag22_explicit_selection_exactly`; release verifier по восьми семействам | SHA-256 immutable-family guards + дифференциальный protocol replay; npm/Swift реплеят прежние семь | +| conformance pack 6 добавляет только atomic explicit-selection family; прежние семь family остаются byte-identical, а 15 outcomes воспроизводятся canonical protocol encoder, собранным compiler-WASM (npm тест + wasm32 паритет) и Swift/FFI побайтно | `pack_v6_contract`; `reference_runner::protocol_reproduces_committed_wcag22_explicit_selection_exactly`; `wcag22-explicit-selection.test.mjs`; compiler `wasm_parity`; Swift ConformanceTests | SHA-256 immutable-family guards + три независимых адаптерных реплея одной protocol-границы | | исторические native-допуски V1–V3 остаются неизменными; V4 заново измеряет тот же neutral-вход после C1 workspace split | V1 checker исполняется в чистом измеренном snapshot `6001cf4`, его applicability verifier — в `94efeee`; V2 побайтно проверяется сохранённым checker-ом из snapshot `4afe61b`; V3 replay-ится в merged Slice-B snapshot `10c44ef`; текущий V4 с artifact SHA `3c257c336bc403eee933990fd7188a3b0a6e89d0cbc983aff18846ef76206275` создаёт только source-bound recorder: exact dependency cone проверяется до fresh build и после запуска, Cargo-config hierarchy пуста, среда закрыта, toolchain и реально запущенный benchmark binary связаны SHA-256; deterministic scenario/identity-проекция V3/V4 одинакова, а изменившийся subject-набор ограничен workspace/admission machinery | fail-closed strict-JSON parser, точная сверка dependency cone и recorder receipt, 71 checker-мутация и сырые native-наблюдения без timing threshold | -| runtime и compiler WASM воспроизводимы как разные execution-role; compiler завершает все объявленные граничные формы полного вызова | append-only size V5 связывает независимые exact Linux x64 size/SHA и рецепты обеих ролей с нулевым запасом, не переписывая V1–V4; whole-call V3 связан с `compiler.js`, compiler glue/WASM, V5 compiler recipe и native admission V4, сохраняет exact request/outcome projection и отдельно наблюдает `initSync` и прогретую операцию | `check-wasm-size-budget.mjs`, role-budget mutation tests и whole-call V3 verifier; независимые Linux `wc`/`sha256sum`; размер применим только к точным артефактам/рецептам, формы — только к `SCENARIO_IDS`, а timing/maxRSS/pages не являются порогами | +| runtime и compiler WASM воспроизводимы как разные execution-role; compiler завершает все объявленные граничные формы полного вызова | append-only size V6 связывает независимые exact Linux x64 size/SHA и рецепты обеих ролей с нулевым запасом, не переписывая V1–V5 (runtime-измерение байт-идентично C1, compiler-рост C3 = добавленная операция); whole-call V5 связан с `compiler.js`, compiler glue/WASM, V6 compiler recipe и native admission V5, сохраняет exact request/outcome projection и отдельно наблюдает `initSync` и прогретую операцию | `check-wasm-size-budget.mjs`, role-budget mutation tests и whole-call V5 verifier; независимые Linux `wc`/`sha256sum`; размер применим только к точным артефактам/рецептам, формы — только к `SCENARIO_IDS`, а timing/maxRSS/pages не являются порогами | Здесь `E` — число канонических применимых рёбер «связь × сосед» (`E∈{0,1,…,2047}`), `A` — число применимых связей (`A∈{0,1,…,E}`; diff --git a/packages/colors/README.md b/packages/colors/README.md index e39b34bc..47230e56 100644 --- a/packages/colors/README.md +++ b/packages/colors/README.md @@ -308,9 +308,9 @@ diagnostics и legacy `wcagRatio` не могут изменить этот ве угадывает применимость и не понимает семантику ID. Операция экспортируется только из `@labpics/colors/compiler` и загружает -отдельный compiler WASM; package root остаётся runtime API. Эта npm-граница -принимает только зарегистрированную нейтральную ось V1. Явные клиентские наборы -sRGB8 доступны в Rust Core и этим transport API не принимаются. +отдельный compiler WASM; package root остаётся runtime API. Эта операция +принимает только зарегистрированную нейтральную ось V1; явные клиентские +наборы sRGB8 обслуживает соседняя атомарная операция ниже. В браузере compiler принадлежит offline/Worker execution class: main thread импортирует только runtime, а dedicated module Worker владеет compiler WASM и @@ -522,6 +522,69 @@ replacement принадлежит #283. --- +### Offline compiler: `evaluateWcag22ExplicitSelection(request)` + +Атомарная операция `wcag22-explicit-selection-v1` для явного клиентского +набора: один строгий запрос несёт конечный домен «opaque ID + точный sRGB8», +объявленные связи, resource profile и политику выбора +(`first-feasible-in-declared-order-v1`). Внутри одного вызова Core полностью +перебирает домен, целиком валидирует политику после любого успешного +терминала, выбирает первый feasible ID объявленного порядка и перепроверяет +каждую применимую грань выбранной строки тем же точным вычислителем. + +Запрос не несёт клиентских счётчиков, дайджестов, матриц, evaluation ID или +proof — всё доказательное состояние выводит и запечатывает Core. +Сериализованный исход — данные для проверки, а не «право выбора»: обратно он +не принимается. Исход — ровно один из `selected | noSelection | infeasible | +notEvaluated` либо типизированный отказ без частичного feasibility. + +```ts +import initCompiler, { + evaluateWcag22ExplicitSelection, + wcag22ExplicitSelectionMaxBytes, + type Wcag22ExplicitSelectionRequestV1, +} from "@labpics/colors/compiler"; + +await initCompiler(); + +const request: Wcag22ExplicitSelectionRequestV1 = { + schemaVersion: 1, + domainId: "explicit-srgb8-set-v1", + resourceProfileId: "compile-v1", + candidates: [ + { candidateId: "brand-600", emitted: [64, 82, 255] }, + { candidateId: "brand-700", emitted: [46, 60, 200] }, + ], + relations: [{ + relationId: "body-text", + occurrenceId: "paragraph", + kind: "applicable", + criterion: "sc-1.4.3-text-default", + adjacent: [[255, 255, 255]], + }], + policy: { + policyKind: "first-feasible-in-declared-order-v1", + policyId: "brand-preference", + orderedCandidateIds: ["brand-600", "brand-700"], + }, +}; + +const bytes = new TextEncoder().encode(JSON.stringify(request)); +if (bytes.byteLength > wcag22ExplicitSelectionMaxBytes()) { + throw new Error("request exceeds the derived envelope"); +} +const outcome = evaluateWcag22ExplicitSelection(bytes); +if (outcome.outcome === "success" && outcome.result.status === "selected") { + // Физическая допустимость доказана Core; выбор — ровно первый feasible ID + // ИЗ ВАШЕГО порядка, с финальной перепроверкой каждой применимой грани. + console.log(outcome.result.selection.candidateId); +} +``` + +Браузерное исполнение — тот же offline/Worker класс, что и у feasibility: +main thread импортирует только типы, dedicated module Worker владеет compiler +WASM и полным вызовом. + ## Размер бандла Raw-размер WASM — hard gate с append-only историей. Текущий diff --git a/packages/colors/bench/wasm-size-budget-v6.json b/packages/colors/bench/wasm-size-budget-v6.json new file mode 100644 index 00000000..85237817 --- /dev/null +++ b/packages/colors/bench/wasm-size-budget-v6.json @@ -0,0 +1,54 @@ +{ + "schemaVersion": 5, + "budgetId": "labcolors-wasm-roles-issue-296-c3-v6", + "predecessor": { + "path": "packages/colors/bench/wasm-size-budget-v5.json", + "fileSha256": "e4b53a2eb976a8c66827a559cb81232e359b734dbfb14725da215cb496ff5d59" + }, + "toolchainSource": { + "path": "packages/colors/bench/wasm-size-budget-v1.json", + "fileSha256": "4f7340fc8cfd0ccb97377c385f2f8d8e7a9ef2c5ba96177f518c5d07de2825e1" + }, + "buildRecipes": { + "runtime": { + "command": "CARGO_ENCODED_RUSTFLAGS= wasm-pack build crates/labcolors-wasm --release --target web --out-dir ../../packages/colors/pkg --out-name labcolors --locked", + "recipeSha256": "0ea74cb070e0a5facb7280f6124930a0bb673ee4dcee9c99fff110db6c9389d4" + }, + "compiler": { + "command": "CARGO_ENCODED_RUSTFLAGS= wasm-pack build crates/labcolors-compiler --release --target web --out-dir ../../packages/colors/compiler --out-name labcolors_compiler --locked", + "recipeSha256": "ce53cea5f579c512a6d2f0c3348f250ac0a5e03206de55e7979c8eae1403be8f" + } + }, + "roles": { + "runtime": { + "artifact": "packages/colors/pkg/labcolors_bg.wasm", + "measurement": { + "issue": 296, + "slice": "C1", + "measurementPlatform": "linux-x64", + "rawBytes": 454385, + "sha256": "8cd65f001d4bb4b8ddead9084e705a64bee14cd796c7bc6ebeb2f2687aa5fdba" + }, + "policy": { + "maxRawBytes": 454385, + "derivation": "exact-accepted-issue-296-slice-c1-runtime-measurement", + "gzip": "diagnostic-only" + } + }, + "compiler": { + "artifact": "packages/colors/compiler/labcolors_compiler_bg.wasm", + "measurement": { + "issue": 296, + "slice": "C3", + "measurementPlatform": "linux-x64", + "rawBytes": 229658, + "sha256": "34e2a561862ee06d52d1104f8ba60ccf9967e2e4fd09803d4e75e1966074bc8d" + }, + "policy": { + "maxRawBytes": 229658, + "derivation": "exact-accepted-issue-296-slice-c3-compiler-measurement", + "gzip": "diagnostic-only" + } + } + } +} diff --git a/packages/colors/bench/wcag22-feasibility-boundary.bench.mjs b/packages/colors/bench/wcag22-feasibility-boundary.bench.mjs index c3bc11ac..f810d182 100644 --- a/packages/colors/bench/wcag22-feasibility-boundary.bench.mjs +++ b/packages/colors/bench/wcag22-feasibility-boundary.bench.mjs @@ -37,14 +37,14 @@ const coreAdmissionPath = resolve( ); const packOraclePath = resolve(repoRoot, "conformance/vectors/wcag22-feasibility.json"); const conformanceManifestPath = resolve(repoRoot, "conformance/vectors/manifest.json"); -const wasmBudgetPath = resolve(here, "wasm-size-budget-v5.json"); +const wasmBudgetPath = resolve(here, "wasm-size-budget-v6.json"); const wasmToolchainSourcePath = resolve(here, "wasm-size-budget-v1.json"); const ciWorkflowPath = resolve(repoRoot, ".github/workflows/ci.yml"); -const defaultMeasurementPath = resolve(here, "wcag22-feasibility-wasm-boundary-v4.json"); +const defaultMeasurementPath = resolve(here, "wcag22-feasibility-wasm-boundary-v5.json"); const pageBytes = 65_536; const candidateCount = 256; -export const MEASUREMENT_ARTIFACT_ID = "wcag22-feasibility-wasm-whole-call-v4"; +export const MEASUREMENT_ARTIFACT_ID = "wcag22-feasibility-wasm-whole-call-v5"; export const SCENARIO_IDS = Object.freeze([ "minimum-evaluated", "maximum-canonical-applicable-relations", @@ -163,10 +163,10 @@ function sourceContracts() { fail("unsupported Core admission artifact identity"); } if ( - budget.value?.schemaVersion !== 4 || - budget.value?.budgetId !== "labcolors-wasm-roles-issue-296-c1-v5" || + budget.value?.schemaVersion !== 5 || + budget.value?.budgetId !== "labcolors-wasm-roles-issue-296-c3-v6" || sha256(budget.bytes) !== - "e4b53a2eb976a8c66827a559cb81232e359b734dbfb14725da215cb496ff5d59" || + "761af6050031169dac7eafdfadb2db9bbb2023b96ed5ba9d3c5dc966ffeafb32" || budget.value?.toolchainSource?.path !== "packages/colors/bench/wasm-size-budget-v1.json" || budget.value?.toolchainSource?.fileSha256 !== sha256(toolchain.bytes) || @@ -677,7 +677,7 @@ export function validateMeasurementArtifact( ); if ( artifact.bindings.wasmBudget.path !== - "packages/colors/bench/wasm-size-budget-v5.json" || + "packages/colors/bench/wasm-size-budget-v6.json" || artifact.bindings.wasmBudget.schemaVersion !== budget.value.schemaVersion || artifact.bindings.wasmBudget.budgetId !== budget.value.budgetId || artifact.bindings.wasmBudget.fileSha256 !== sha256(budget.bytes) || @@ -1230,7 +1230,7 @@ function measurementArtifact() { }, compilerSources: sources, wasmBudget: { - path: "packages/colors/bench/wasm-size-budget-v5.json", + path: "packages/colors/bench/wasm-size-budget-v6.json", schemaVersion: budget.value.schemaVersion, budgetId: budget.value.budgetId, fileSha256: sha256(budget.bytes), diff --git a/packages/colors/bench/wcag22-feasibility-wasm-boundary-v5.json b/packages/colors/bench/wcag22-feasibility-wasm-boundary-v5.json new file mode 100644 index 00000000..56ef5228 --- /dev/null +++ b/packages/colors/bench/wcag22-feasibility-wasm-boundary-v5.json @@ -0,0 +1 @@ +{"schemaVersion":1,"artifactId":"wcag22-feasibility-wasm-whole-call-v5","claimBoundary":"canonical-wasm-compiler-entry-whole-call-observations-only","claims":{"admission":"canonical-linux-x64-exact-wasm-only","hardGates":["completion","request-and-outcome-bytes","sha256-binding","terminal-algebra","packed-shape","candidate-major-lsb0-pack-oracle","no-proportional-dto"],"timingThresholdNs":null,"latency":"init-sync-and-warm-operation-observations-only-no-production-threshold","memory":"process maxRSS values are total-process high-water including V8 and prior warm-up/observer allocations; after-init and warm-call WASM pages are linear-memory high-water observations; neither is total operation memory"},"environment":{"execution":"fresh-node-child-process-per-sample","initSyncScope":"initSync-from-in-memory-compiler-wasm-includes-wasm-bindgen-startup-excludes-io-and-js-module-import","operationScope":"second-identical-operation-after-one-unmeasured-warm-up-whose-result-graph-is-not-retained-by-harness","platform":"linux-x64","nodeVersion":"v24.14.0","sampleCount":5,"requestConstructionMeasured":false,"timer":"process.hrtime.bigint","publicEntry":"packages/colors/compiler.js","canonicalCandidate":true,"rustToolchain":"1.96.0","wasmPack":"0.13.1","wasmBindgen":"0.2.126","target":"wasm32-unknown-unknown","cargoProfile":"release","wasmOpt":"-Oz"},"bindings":{"coreAdmission":{"path":"crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v5.json","schemaVersion":1,"artifactId":"wcag22-feasibility-admission-raw-v5","profileId":"compile-v1","sha256":"6079612797bb28a9fc97c1451efd830e2323ed06e15fbffd70527dddc3fa84c5"},"packOracle":{"path":"conformance/vectors/wcag22-feasibility.json","sha256":"ae2caec47a7b650e73b8d4029a69b4e401dfb7cc199db579c0f95106eebe8dc3","caseId":"text-default-seven","requestSha256":"8da46d4cb835c4e3c89b8f56ec4025457e0e4308ed28adcfa7beb134858e3d6f","outcomeSha256":"9dbe5499abfd07cd915da6da0ca1578278aebc33b0f24c41c6318858026ff442","manifestPath":"conformance/vectors/manifest.json","manifestSha256":"7351b12172f6ed4d1bbce3cdb1eab80f8c526f669980e15bce357da9e6f2e483","packVersion":"6.0.0","packDigest":"129158d2"},"compilerSources":{"compilerEntry":{"path":"packages/colors/compiler.js","sha256":"5811b4475535f17d836ec165a5577064054a20bfce9a2390668e52bec075135f"},"harness":{"path":"packages/colors/bench/wcag22-feasibility-boundary.bench.mjs","sha256":"d5993bd85cb628487c8344b3e4764f8943dbf60f38b358c6fb40e7680b8fa223"},"packageManifest":{"path":"packages/colors/package.json","sha256":"0c4927abe3ae2ac6919e73ea76b59dd7a17ada0d04c8ec2f95000ad2cfeae865"},"wasmGlue":{"path":"packages/colors/compiler/labcolors_compiler.js","sha256":"5a7595609cd8693bec69096092fbc45b14121c0087105b5bed4fb7e8a8466727"}},"wasmBudget":{"path":"packages/colors/bench/wasm-size-budget-v6.json","schemaVersion":5,"budgetId":"labcolors-wasm-roles-issue-296-c3-v6","fileSha256":"761af6050031169dac7eafdfadb2db9bbb2023b96ed5ba9d3c5dc966ffeafb32","role":"compiler","recipeSha256":"ce53cea5f579c512a6d2f0c3348f250ac0a5e03206de55e7979c8eae1403be8f"},"wasm":{"path":"packages/colors/compiler/labcolors_compiler_bg.wasm","bytes":229658,"sha256":"34e2a561862ee06d52d1104f8ba60ccf9967e2e4fd09803d4e75e1966074bc8d"}},"limits":{"maxRequestBytes":657380,"profileId":"compile-v1","rawRelations":2047,"rawAdjacentEntries":2047,"opaqueUtf8Bytes":65536,"canonicalRelations":2047,"applicableEdges":2047,"logicalAssessments":524032,"packedResultBytes":65536},"scenarios":[{"scenarioId":"minimum-evaluated","shape":{"rawRelations":1,"rawAdjacentEntries":1,"opaqueUtf8Bytes":2,"canonicalRelations":1,"applicableRelations":1,"applicableEdges":1},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"2732261","elapsedNs":"350268","requestBytes":222,"requestSha256":"21ff2cc1a9b3da86e633f999a5c8417f0be059c499fc9114490e274b319d2ee7","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"lsb0PackOracleMatches":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52628,"processMaxRssKiBAfterInit":53624,"processMaxRssKiBBefore":56460,"processMaxRssKiBAfter":56460,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":1,"initSyncElapsedNs":"917975","elapsedNs":"935401","requestBytes":222,"requestSha256":"21ff2cc1a9b3da86e633f999a5c8417f0be059c499fc9114490e274b319d2ee7","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"lsb0PackOracleMatches":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52552,"processMaxRssKiBAfterInit":53764,"processMaxRssKiBBefore":56364,"processMaxRssKiBAfter":56364,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":2,"initSyncElapsedNs":"1094641","elapsedNs":"365230","requestBytes":222,"requestSha256":"21ff2cc1a9b3da86e633f999a5c8417f0be059c499fc9114490e274b319d2ee7","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"lsb0PackOracleMatches":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52532,"processMaxRssKiBAfterInit":53872,"processMaxRssKiBBefore":56600,"processMaxRssKiBAfter":56600,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":3,"initSyncElapsedNs":"1029224","elapsedNs":"407895","requestBytes":222,"requestSha256":"21ff2cc1a9b3da86e633f999a5c8417f0be059c499fc9114490e274b319d2ee7","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"lsb0PackOracleMatches":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52736,"processMaxRssKiBAfterInit":53820,"processMaxRssKiBBefore":56932,"processMaxRssKiBAfter":56932,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":4,"initSyncElapsedNs":"4247698","elapsedNs":"408506","requestBytes":222,"requestSha256":"21ff2cc1a9b3da86e633f999a5c8417f0be059c499fc9114490e274b319d2ee7","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"lsb0PackOracleMatches":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52568,"processMaxRssKiBAfterInit":53652,"processMaxRssKiBBefore":56948,"processMaxRssKiBAfter":56948,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18}]},{"scenarioId":"maximum-canonical-applicable-relations","shape":{"rawRelations":2047,"rawAdjacentEntries":2047,"opaqueUtf8Bytes":20470,"canonicalRelations":2047,"applicableRelations":2047,"applicableEdges":2047},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"1021091","elapsedNs":"89241518","requestBytes":264164,"requestSha256":"83955d22987a29e4df0c91647cecadcb3a7e7f5bc06010e0b05519fac4b938d2","outcomeBytes":527090,"outcomeSha256":"dedc563b2a3f7bee7a0f37c5976af853814ac4e9ce12f27efaa3ec8661a79b1c","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52660,"processMaxRssKiBAfterInit":54000,"processMaxRssKiBBefore":73516,"processMaxRssKiBAfter":75308,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4194304,"wasmMemoryBytesAfter":4194304,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":64,"wasmMemoryPagesAfter":64},{"sampleIndex":1,"initSyncElapsedNs":"990584","elapsedNs":"92592073","requestBytes":264164,"requestSha256":"83955d22987a29e4df0c91647cecadcb3a7e7f5bc06010e0b05519fac4b938d2","outcomeBytes":527090,"outcomeSha256":"dedc563b2a3f7bee7a0f37c5976af853814ac4e9ce12f27efaa3ec8661a79b1c","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52644,"processMaxRssKiBAfterInit":53728,"processMaxRssKiBBefore":72792,"processMaxRssKiBAfter":74840,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4194304,"wasmMemoryBytesAfter":4194304,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":64,"wasmMemoryPagesAfter":64},{"sampleIndex":2,"initSyncElapsedNs":"1229574","elapsedNs":"86545130","requestBytes":264164,"requestSha256":"83955d22987a29e4df0c91647cecadcb3a7e7f5bc06010e0b05519fac4b938d2","outcomeBytes":527090,"outcomeSha256":"dedc563b2a3f7bee7a0f37c5976af853814ac4e9ce12f27efaa3ec8661a79b1c","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52788,"processMaxRssKiBAfterInit":54000,"processMaxRssKiBBefore":74260,"processMaxRssKiBAfter":76052,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4194304,"wasmMemoryBytesAfter":4194304,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":64,"wasmMemoryPagesAfter":64},{"sampleIndex":3,"initSyncElapsedNs":"1060891","elapsedNs":"87170104","requestBytes":264164,"requestSha256":"83955d22987a29e4df0c91647cecadcb3a7e7f5bc06010e0b05519fac4b938d2","outcomeBytes":527090,"outcomeSha256":"dedc563b2a3f7bee7a0f37c5976af853814ac4e9ce12f27efaa3ec8661a79b1c","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52708,"processMaxRssKiBAfterInit":53792,"processMaxRssKiBBefore":74724,"processMaxRssKiBAfter":76516,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4194304,"wasmMemoryBytesAfter":4194304,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":64,"wasmMemoryPagesAfter":64},{"sampleIndex":4,"initSyncElapsedNs":"4493607","elapsedNs":"92102184","requestBytes":264164,"requestSha256":"83955d22987a29e4df0c91647cecadcb3a7e7f5bc06010e0b05519fac4b938d2","outcomeBytes":527090,"outcomeSha256":"dedc563b2a3f7bee7a0f37c5976af853814ac4e9ce12f27efaa3ec8661a79b1c","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52564,"processMaxRssKiBAfterInit":53776,"processMaxRssKiBBefore":72924,"processMaxRssKiBAfter":75484,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4194304,"wasmMemoryBytesAfter":4194304,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":64,"wasmMemoryPagesAfter":64}]},{"scenarioId":"maximum-applicable-edges","shape":{"rawRelations":1,"rawAdjacentEntries":2047,"opaqueUtf8Bytes":19,"canonicalRelations":1,"applicableRelations":1,"applicableEdges":2047},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"1061070","elapsedNs":"87081150","requestBytes":23909,"requestSha256":"9a60a035743121bc4b79660f3a76f75ff1d3da2c5b3a243449442f972e8af9ae","outcomeBytes":269795,"outcomeSha256":"7f08a3d65184a40adc8de04edc352511130fe6fc8e223729996a11cd18bc622c","summary":{"outcome":"success","terminal":"infeasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":0,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52668,"processMaxRssKiBAfterInit":53880,"processMaxRssKiBBefore":66692,"processMaxRssKiBAfter":67844,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2162688,"wasmMemoryBytesAfter":2162688,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":33,"wasmMemoryPagesAfter":33},{"sampleIndex":1,"initSyncElapsedNs":"1105057","elapsedNs":"87737010","requestBytes":23909,"requestSha256":"9a60a035743121bc4b79660f3a76f75ff1d3da2c5b3a243449442f972e8af9ae","outcomeBytes":269795,"outcomeSha256":"7f08a3d65184a40adc8de04edc352511130fe6fc8e223729996a11cd18bc622c","summary":{"outcome":"success","terminal":"infeasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":0,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52848,"processMaxRssKiBAfterInit":53932,"processMaxRssKiBBefore":66336,"processMaxRssKiBAfter":67872,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2162688,"wasmMemoryBytesAfter":2162688,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":33,"wasmMemoryPagesAfter":33},{"sampleIndex":2,"initSyncElapsedNs":"1147040","elapsedNs":"89188548","requestBytes":23909,"requestSha256":"9a60a035743121bc4b79660f3a76f75ff1d3da2c5b3a243449442f972e8af9ae","outcomeBytes":269795,"outcomeSha256":"7f08a3d65184a40adc8de04edc352511130fe6fc8e223729996a11cd18bc622c","summary":{"outcome":"success","terminal":"infeasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":0,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":50888,"processMaxRssKiBAfterInit":51972,"processMaxRssKiBBefore":65000,"processMaxRssKiBAfter":66664,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2162688,"wasmMemoryBytesAfter":2162688,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":33,"wasmMemoryPagesAfter":33},{"sampleIndex":3,"initSyncElapsedNs":"1156446","elapsedNs":"86914078","requestBytes":23909,"requestSha256":"9a60a035743121bc4b79660f3a76f75ff1d3da2c5b3a243449442f972e8af9ae","outcomeBytes":269795,"outcomeSha256":"7f08a3d65184a40adc8de04edc352511130fe6fc8e223729996a11cd18bc622c","summary":{"outcome":"success","terminal":"infeasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":0,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":50644,"processMaxRssKiBAfterInit":51852,"processMaxRssKiBBefore":65084,"processMaxRssKiBAfter":66236,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2162688,"wasmMemoryBytesAfter":2162688,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":33,"wasmMemoryPagesAfter":33},{"sampleIndex":4,"initSyncElapsedNs":"4305464","elapsedNs":"90420177","requestBytes":23909,"requestSha256":"9a60a035743121bc4b79660f3a76f75ff1d3da2c5b3a243449442f972e8af9ae","outcomeBytes":269795,"outcomeSha256":"7f08a3d65184a40adc8de04edc352511130fe6fc8e223729996a11cd18bc622c","summary":{"outcome":"success","terminal":"infeasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":0,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":50844,"processMaxRssKiBAfterInit":52056,"processMaxRssKiBBefore":64348,"processMaxRssKiBAfter":65628,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2162688,"wasmMemoryBytesAfter":2162688,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":33,"wasmMemoryPagesAfter":33}]},{"scenarioId":"maximum-opaque-utf8-bytes","shape":{"rawRelations":1,"rawAdjacentEntries":0,"opaqueUtf8Bytes":65536,"canonicalRelations":1,"applicableRelations":0,"applicableEdges":0},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"4187947","elapsedNs":"1519371","requestBytes":65710,"requestSha256":"4c2106974c30967456ac4d92bced32418cb0cb9b8f9ba97e53f19e0f629cf1f2","outcomeBytes":66050,"outcomeSha256":"4651c1ed3d6d43ef8d0eeaf78bb745d4b58ba7d160877df4a23433b233869f18","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"1","applicableRelations":"0","notApplicableRelations":"1","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52436,"processMaxRssKiBAfterInit":53776,"processMaxRssKiBBefore":60084,"processMaxRssKiBAfter":61020,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1507328,"wasmMemoryBytesAfter":1507328,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":23,"wasmMemoryPagesAfter":23},{"sampleIndex":1,"initSyncElapsedNs":"1000870","elapsedNs":"1382916","requestBytes":65710,"requestSha256":"4c2106974c30967456ac4d92bced32418cb0cb9b8f9ba97e53f19e0f629cf1f2","outcomeBytes":66050,"outcomeSha256":"4651c1ed3d6d43ef8d0eeaf78bb745d4b58ba7d160877df4a23433b233869f18","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"1","applicableRelations":"0","notApplicableRelations":"1","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52556,"processMaxRssKiBAfterInit":53768,"processMaxRssKiBBefore":59756,"processMaxRssKiBAfter":60820,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1507328,"wasmMemoryBytesAfter":1507328,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":23,"wasmMemoryPagesAfter":23},{"sampleIndex":2,"initSyncElapsedNs":"1016224","elapsedNs":"1872755","requestBytes":65710,"requestSha256":"4c2106974c30967456ac4d92bced32418cb0cb9b8f9ba97e53f19e0f629cf1f2","outcomeBytes":66050,"outcomeSha256":"4651c1ed3d6d43ef8d0eeaf78bb745d4b58ba7d160877df4a23433b233869f18","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"1","applicableRelations":"0","notApplicableRelations":"1","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52532,"processMaxRssKiBAfterInit":53740,"processMaxRssKiBBefore":60348,"processMaxRssKiBAfter":61116,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1507328,"wasmMemoryBytesAfter":1507328,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":23,"wasmMemoryPagesAfter":23},{"sampleIndex":3,"initSyncElapsedNs":"1031075","elapsedNs":"692104","requestBytes":65710,"requestSha256":"4c2106974c30967456ac4d92bced32418cb0cb9b8f9ba97e53f19e0f629cf1f2","outcomeBytes":66050,"outcomeSha256":"4651c1ed3d6d43ef8d0eeaf78bb745d4b58ba7d160877df4a23433b233869f18","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"1","applicableRelations":"0","notApplicableRelations":"1","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52560,"processMaxRssKiBAfterInit":53900,"processMaxRssKiBBefore":60892,"processMaxRssKiBAfter":60892,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1507328,"wasmMemoryBytesAfter":1507328,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":23,"wasmMemoryPagesAfter":23},{"sampleIndex":4,"initSyncElapsedNs":"1020569","elapsedNs":"676120","requestBytes":65710,"requestSha256":"4c2106974c30967456ac4d92bced32418cb0cb9b8f9ba97e53f19e0f629cf1f2","outcomeBytes":66050,"outcomeSha256":"4651c1ed3d6d43ef8d0eeaf78bb745d4b58ba7d160877df4a23433b233869f18","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"1","applicableRelations":"0","notApplicableRelations":"1","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52572,"processMaxRssKiBAfterInit":53784,"processMaxRssKiBBefore":60840,"processMaxRssKiBAfter":60840,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1507328,"wasmMemoryBytesAfter":1507328,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":23,"wasmMemoryPagesAfter":23}]},{"scenarioId":"maximum-canonical-not-applicable-relations","shape":{"rawRelations":2047,"rawAdjacentEntries":0,"opaqueUtf8Bytes":36846,"canonicalRelations":2047,"applicableRelations":0,"applicableEdges":0},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"906257","elapsedNs":"6524210","requestBytes":186378,"requestSha256":"b92c8700a4f6f6979e9476d56e57166618971aaecdbf4445ccd6aac124f821b7","outcomeBytes":186719,"outcomeSha256":"bf21a25ad92cfefad9c7116c16f918d97dab5e18e610a21482b26fdf5a948d69","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52696,"processMaxRssKiBAfterInit":53780,"processMaxRssKiBBefore":67672,"processMaxRssKiBAfter":68568,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2097152,"wasmMemoryBytesAfter":2097152,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":32,"wasmMemoryPagesAfter":32},{"sampleIndex":1,"initSyncElapsedNs":"1388895","elapsedNs":"6695980","requestBytes":186378,"requestSha256":"b92c8700a4f6f6979e9476d56e57166618971aaecdbf4445ccd6aac124f821b7","outcomeBytes":186719,"outcomeSha256":"bf21a25ad92cfefad9c7116c16f918d97dab5e18e610a21482b26fdf5a948d69","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52560,"processMaxRssKiBAfterInit":53772,"processMaxRssKiBBefore":67988,"processMaxRssKiBAfter":68976,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2097152,"wasmMemoryBytesAfter":2097152,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":32,"wasmMemoryPagesAfter":32},{"sampleIndex":2,"initSyncElapsedNs":"857433","elapsedNs":"6863614","requestBytes":186378,"requestSha256":"b92c8700a4f6f6979e9476d56e57166618971aaecdbf4445ccd6aac124f821b7","outcomeBytes":186719,"outcomeSha256":"bf21a25ad92cfefad9c7116c16f918d97dab5e18e610a21482b26fdf5a948d69","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52704,"processMaxRssKiBAfterInit":53792,"processMaxRssKiBBefore":66684,"processMaxRssKiBAfter":67292,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2097152,"wasmMemoryBytesAfter":2097152,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":32,"wasmMemoryPagesAfter":32},{"sampleIndex":3,"initSyncElapsedNs":"1218017","elapsedNs":"6502939","requestBytes":186378,"requestSha256":"b92c8700a4f6f6979e9476d56e57166618971aaecdbf4445ccd6aac124f821b7","outcomeBytes":186719,"outcomeSha256":"bf21a25ad92cfefad9c7116c16f918d97dab5e18e610a21482b26fdf5a948d69","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52452,"processMaxRssKiBAfterInit":53536,"processMaxRssKiBBefore":67724,"processMaxRssKiBAfter":69260,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2097152,"wasmMemoryBytesAfter":2097152,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":32,"wasmMemoryPagesAfter":32},{"sampleIndex":4,"initSyncElapsedNs":"949852","elapsedNs":"6083317","requestBytes":186378,"requestSha256":"b92c8700a4f6f6979e9476d56e57166618971aaecdbf4445ccd6aac124f821b7","outcomeBytes":186719,"outcomeSha256":"bf21a25ad92cfefad9c7116c16f918d97dab5e18e610a21482b26fdf5a948d69","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52588,"processMaxRssKiBAfterInit":53800,"processMaxRssKiBBefore":67644,"processMaxRssKiBAfter":68540,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":2097152,"wasmMemoryBytesAfter":2097152,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":32,"wasmMemoryPagesAfter":32}]},{"scenarioId":"maximum-combined-not-applicable-envelope","shape":{"rawRelations":2047,"rawAdjacentEntries":0,"opaqueUtf8Bytes":65536,"canonicalRelations":2047,"applicableRelations":0,"applicableEdges":0},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"1010214","elapsedNs":"9042860","requestBytes":542748,"requestSha256":"25ecc80095ed218f461bfe5283fa19d3e440eb603d49e8b65b65f5ec57a92ece","outcomeBytes":543093,"outcomeSha256":"dab7eda461b67a099559b2419b077c541991f62263f5e393322f278164835c33","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52656,"processMaxRssKiBAfterInit":53740,"processMaxRssKiBBefore":70488,"processMaxRssKiBAfter":72152,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4128768,"wasmMemoryBytesAfter":4128768,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":63,"wasmMemoryPagesAfter":63},{"sampleIndex":1,"initSyncElapsedNs":"1050645","elapsedNs":"9143903","requestBytes":542748,"requestSha256":"25ecc80095ed218f461bfe5283fa19d3e440eb603d49e8b65b65f5ec57a92ece","outcomeBytes":543093,"outcomeSha256":"dab7eda461b67a099559b2419b077c541991f62263f5e393322f278164835c33","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52992,"processMaxRssKiBAfterInit":54204,"processMaxRssKiBBefore":70516,"processMaxRssKiBAfter":72180,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4128768,"wasmMemoryBytesAfter":4128768,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":63,"wasmMemoryPagesAfter":63},{"sampleIndex":2,"initSyncElapsedNs":"1397018","elapsedNs":"30542718","requestBytes":542748,"requestSha256":"25ecc80095ed218f461bfe5283fa19d3e440eb603d49e8b65b65f5ec57a92ece","outcomeBytes":543093,"outcomeSha256":"dab7eda461b67a099559b2419b077c541991f62263f5e393322f278164835c33","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52888,"processMaxRssKiBAfterInit":54100,"processMaxRssKiBBefore":72988,"processMaxRssKiBAfter":74268,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4128768,"wasmMemoryBytesAfter":4128768,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":63,"wasmMemoryPagesAfter":63},{"sampleIndex":3,"initSyncElapsedNs":"1196325","elapsedNs":"9216812","requestBytes":542748,"requestSha256":"25ecc80095ed218f461bfe5283fa19d3e440eb603d49e8b65b65f5ec57a92ece","outcomeBytes":543093,"outcomeSha256":"dab7eda461b67a099559b2419b077c541991f62263f5e393322f278164835c33","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52900,"processMaxRssKiBAfterInit":53984,"processMaxRssKiBBefore":72676,"processMaxRssKiBAfter":73956,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4128768,"wasmMemoryBytesAfter":4128768,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":63,"wasmMemoryPagesAfter":63},{"sampleIndex":4,"initSyncElapsedNs":"1586182","elapsedNs":"14195523","requestBytes":542748,"requestSha256":"25ecc80095ed218f461bfe5283fa19d3e440eb603d49e8b65b65f5ec57a92ece","outcomeBytes":543093,"outcomeSha256":"dab7eda461b67a099559b2419b077c541991f62263f5e393322f278164835c33","summary":{"outcome":"success","terminal":"notEvaluated","canonicalRelations":"2047","applicableRelations":"0","notApplicableRelations":"2047","numericalEvidencePresent":false,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52852,"processMaxRssKiBAfterInit":54064,"processMaxRssKiBBefore":70848,"processMaxRssKiBAfter":72640,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4128768,"wasmMemoryBytesAfter":4128768,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":63,"wasmMemoryPagesAfter":63}]},{"scenarioId":"maximum-combined-applicable-envelope","shape":{"rawRelations":2047,"rawAdjacentEntries":2047,"opaqueUtf8Bytes":65536,"canonicalRelations":2047,"applicableRelations":2047,"applicableEdges":2047},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"2215001","elapsedNs":"89192194","requestBytes":657380,"requestSha256":"53bbc4fa41ce062a2056151b8e5f7ae68a8d81b122e6178bc919aa749b9898c3","outcomeBytes":847668,"outcomeSha256":"885a51a2136c2a09189bd5c88c35b886d343f99917a3c55f7231b2b37203e270","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":149,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":53012,"processMaxRssKiBAfterInit":54224,"processMaxRssKiBBefore":78404,"processMaxRssKiBAfter":80324,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4653056,"wasmMemoryBytesAfter":4653056,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":71,"wasmMemoryPagesAfter":71},{"sampleIndex":1,"initSyncElapsedNs":"1145297","elapsedNs":"108467238","requestBytes":657380,"requestSha256":"53bbc4fa41ce062a2056151b8e5f7ae68a8d81b122e6178bc919aa749b9898c3","outcomeBytes":847668,"outcomeSha256":"885a51a2136c2a09189bd5c88c35b886d343f99917a3c55f7231b2b37203e270","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":149,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52996,"processMaxRssKiBAfterInit":54208,"processMaxRssKiBBefore":76476,"processMaxRssKiBAfter":78396,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4653056,"wasmMemoryBytesAfter":4653056,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":71,"wasmMemoryPagesAfter":71},{"sampleIndex":2,"initSyncElapsedNs":"1022773","elapsedNs":"103762391","requestBytes":657380,"requestSha256":"53bbc4fa41ce062a2056151b8e5f7ae68a8d81b122e6178bc919aa749b9898c3","outcomeBytes":847668,"outcomeSha256":"885a51a2136c2a09189bd5c88c35b886d343f99917a3c55f7231b2b37203e270","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":149,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52532,"processMaxRssKiBAfterInit":53740,"processMaxRssKiBBefore":78280,"processMaxRssKiBAfter":80072,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4653056,"wasmMemoryBytesAfter":4653056,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":71,"wasmMemoryPagesAfter":71},{"sampleIndex":3,"initSyncElapsedNs":"994080","elapsedNs":"96957807","requestBytes":657380,"requestSha256":"53bbc4fa41ce062a2056151b8e5f7ae68a8d81b122e6178bc919aa749b9898c3","outcomeBytes":847668,"outcomeSha256":"885a51a2136c2a09189bd5c88c35b886d343f99917a3c55f7231b2b37203e270","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":149,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52960,"processMaxRssKiBAfterInit":54172,"processMaxRssKiBBefore":75024,"processMaxRssKiBAfter":77456,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4653056,"wasmMemoryBytesAfter":4653056,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":71,"wasmMemoryPagesAfter":71},{"sampleIndex":4,"initSyncElapsedNs":"933318","elapsedNs":"81075701","requestBytes":657380,"requestSha256":"53bbc4fa41ce062a2056151b8e5f7ae68a8d81b122e6178bc919aa749b9898c3","outcomeBytes":847668,"outcomeSha256":"885a51a2136c2a09189bd5c88c35b886d343f99917a3c55f7231b2b37203e270","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"2047","applicableRelations":"2047","notApplicableRelations":"0","applicableEdges":"2047","logicalAssessments":"524032","failureMatrixBytes":65504,"partitionBytes":32,"feasibleCandidates":149,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52688,"processMaxRssKiBAfterInit":54028,"processMaxRssKiBBefore":77824,"processMaxRssKiBAfter":79872,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":4653056,"wasmMemoryBytesAfter":4653056,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":71,"wasmMemoryPagesAfter":71}]},{"scenarioId":"maximum-raw-duplicate-relations","shape":{"rawRelations":2047,"rawAdjacentEntries":2047,"opaqueUtf8Bytes":26611,"canonicalRelations":1,"applicableRelations":1,"applicableEdges":1},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"951173","elapsedNs":"4376491","requestBytes":270305,"requestSha256":"fc5f72ce71f8f9d81164dbe7632ac3345fca31c2eb677db9709c45bd7a04077a","outcomeBytes":4837,"outcomeSha256":"ef8e4cf8ac7580773571fa354b6b2eed38e4ca60d32780b0ba86e2be8cf6bcf2","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52608,"processMaxRssKiBAfterInit":53944,"processMaxRssKiBBefore":65640,"processMaxRssKiBAfter":65640,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1703936,"wasmMemoryBytesAfter":1703936,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":26,"wasmMemoryPagesAfter":26},{"sampleIndex":1,"initSyncElapsedNs":"1134061","elapsedNs":"4193155","requestBytes":270305,"requestSha256":"fc5f72ce71f8f9d81164dbe7632ac3345fca31c2eb677db9709c45bd7a04077a","outcomeBytes":4837,"outcomeSha256":"ef8e4cf8ac7580773571fa354b6b2eed38e4ca60d32780b0ba86e2be8cf6bcf2","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52696,"processMaxRssKiBAfterInit":53904,"processMaxRssKiBBefore":65992,"processMaxRssKiBAfter":66376,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1703936,"wasmMemoryBytesAfter":1703936,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":26,"wasmMemoryPagesAfter":26},{"sampleIndex":2,"initSyncElapsedNs":"1046879","elapsedNs":"7194342","requestBytes":270305,"requestSha256":"fc5f72ce71f8f9d81164dbe7632ac3345fca31c2eb677db9709c45bd7a04077a","outcomeBytes":4837,"outcomeSha256":"ef8e4cf8ac7580773571fa354b6b2eed38e4ca60d32780b0ba86e2be8cf6bcf2","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52744,"processMaxRssKiBAfterInit":54084,"processMaxRssKiBBefore":65284,"processMaxRssKiBAfter":65284,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1703936,"wasmMemoryBytesAfter":1703936,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":26,"wasmMemoryPagesAfter":26},{"sampleIndex":3,"initSyncElapsedNs":"1296927","elapsedNs":"5514207","requestBytes":270305,"requestSha256":"fc5f72ce71f8f9d81164dbe7632ac3345fca31c2eb677db9709c45bd7a04077a","outcomeBytes":4837,"outcomeSha256":"ef8e4cf8ac7580773571fa354b6b2eed38e4ca60d32780b0ba86e2be8cf6bcf2","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52888,"processMaxRssKiBAfterInit":54100,"processMaxRssKiBBefore":65788,"processMaxRssKiBAfter":66556,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1703936,"wasmMemoryBytesAfter":1703936,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":26,"wasmMemoryPagesAfter":26},{"sampleIndex":4,"initSyncElapsedNs":"1250637","elapsedNs":"5379666","requestBytes":270305,"requestSha256":"fc5f72ce71f8f9d81164dbe7632ac3345fca31c2eb677db9709c45bd7a04077a","outcomeBytes":4837,"outcomeSha256":"ef8e4cf8ac7580773571fa354b6b2eed38e4ca60d32780b0ba86e2be8cf6bcf2","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52668,"processMaxRssKiBAfterInit":53880,"processMaxRssKiBBefore":65712,"processMaxRssKiBAfter":65912,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1703936,"wasmMemoryBytesAfter":1703936,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":26,"wasmMemoryPagesAfter":26}]},{"scenarioId":"maximum-raw-adjacent-duplicates","shape":{"rawRelations":1,"rawAdjacentEntries":2047,"opaqueUtf8Bytes":2,"canonicalRelations":1,"applicableRelations":1,"applicableEdges":1},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"1124477","elapsedNs":"1455175","requestBytes":28866,"requestSha256":"495db8cf72c85e1811f87180cefb4a90c8d64be1277ad4ed41562a2e97034979","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52628,"processMaxRssKiBAfterInit":53840,"processMaxRssKiBBefore":61432,"processMaxRssKiBAfter":61560,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1376256,"wasmMemoryBytesAfter":1376256,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":21,"wasmMemoryPagesAfter":21},{"sampleIndex":1,"initSyncElapsedNs":"991135","elapsedNs":"1446852","requestBytes":28866,"requestSha256":"495db8cf72c85e1811f87180cefb4a90c8d64be1277ad4ed41562a2e97034979","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52680,"processMaxRssKiBAfterInit":53892,"processMaxRssKiBBefore":61416,"processMaxRssKiBAfter":61532,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1376256,"wasmMemoryBytesAfter":1376256,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":21,"wasmMemoryPagesAfter":21},{"sampleIndex":2,"initSyncElapsedNs":"1006618","elapsedNs":"2016892","requestBytes":28866,"requestSha256":"495db8cf72c85e1811f87180cefb4a90c8d64be1277ad4ed41562a2e97034979","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52796,"processMaxRssKiBAfterInit":53880,"processMaxRssKiBBefore":62420,"processMaxRssKiBAfter":62548,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1376256,"wasmMemoryBytesAfter":1376256,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":21,"wasmMemoryPagesAfter":21},{"sampleIndex":3,"initSyncElapsedNs":"1065207","elapsedNs":"1368835","requestBytes":28866,"requestSha256":"495db8cf72c85e1811f87180cefb4a90c8d64be1277ad4ed41562a2e97034979","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52768,"processMaxRssKiBAfterInit":54108,"processMaxRssKiBBefore":61256,"processMaxRssKiBAfter":61256,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1376256,"wasmMemoryBytesAfter":1376256,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":21,"wasmMemoryPagesAfter":21},{"sampleIndex":4,"initSyncElapsedNs":"4218483","elapsedNs":"1845714","requestBytes":28866,"requestSha256":"495db8cf72c85e1811f87180cefb4a90c8d64be1277ad4ed41562a2e97034979","outcomeBytes":4823,"outcomeSha256":"a6196a5aaec38b74029c0d6f5016b9249008d7cd9544c3d5cf5de9932770e090","summary":{"outcome":"success","terminal":"feasible","domainCount":"256","canonicalRelations":"1","applicableRelations":"1","notApplicableRelations":"0","applicableEdges":"1","logicalAssessments":"256","failureMatrixBytes":32,"partitionBytes":32,"feasibleCandidates":7,"lsb0PartitionMatchesMatrix":true,"proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52592,"processMaxRssKiBAfterInit":53676,"processMaxRssKiBBefore":61400,"processMaxRssKiBAfter":61912,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1376256,"wasmMemoryBytesAfter":1376256,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":21,"wasmMemoryPagesAfter":21}]},{"scenarioId":"transport-limit-plus-one","shape":{"rawRelations":2047,"rawAdjacentEntries":2047,"opaqueUtf8Bytes":65536,"canonicalRelations":2047,"applicableRelations":2047,"applicableEdges":2047},"samples":[{"sampleIndex":0,"initSyncElapsedNs":"1199859","elapsedNs":"41332","requestBytes":657381,"requestSha256":"8569362aca144e7f9b4f83af9f9b91ef6287768b91afef3a2feb4d9d2e27606d","outcomeBytes":154,"outcomeSha256":"1a074b69790ebf713dcd65a76ddc630a75ce996ae01404a552583a2211b5e125","summary":{"outcome":"failure","source":"transport","code":"envelopeTooLarge","requestedBytes":"657381","limitBytes":"657380","proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52688,"processMaxRssKiBAfterInit":53772,"processMaxRssKiBBefore":93644,"processMaxRssKiBAfter":93644,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":1,"initSyncElapsedNs":"1081640","elapsedNs":"42135","requestBytes":657381,"requestSha256":"8569362aca144e7f9b4f83af9f9b91ef6287768b91afef3a2feb4d9d2e27606d","outcomeBytes":154,"outcomeSha256":"1a074b69790ebf713dcd65a76ddc630a75ce996ae01404a552583a2211b5e125","summary":{"outcome":"failure","source":"transport","code":"envelopeTooLarge","requestedBytes":"657381","limitBytes":"657380","proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52424,"processMaxRssKiBAfterInit":53632,"processMaxRssKiBBefore":93076,"processMaxRssKiBAfter":93076,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":2,"initSyncElapsedNs":"1229514","elapsedNs":"44827","requestBytes":657381,"requestSha256":"8569362aca144e7f9b4f83af9f9b91ef6287768b91afef3a2feb4d9d2e27606d","outcomeBytes":154,"outcomeSha256":"1a074b69790ebf713dcd65a76ddc630a75ce996ae01404a552583a2211b5e125","summary":{"outcome":"failure","source":"transport","code":"envelopeTooLarge","requestedBytes":"657381","limitBytes":"657380","proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52964,"processMaxRssKiBAfterInit":54048,"processMaxRssKiBBefore":93740,"processMaxRssKiBAfter":93740,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":3,"initSyncElapsedNs":"1023414","elapsedNs":"47942","requestBytes":657381,"requestSha256":"8569362aca144e7f9b4f83af9f9b91ef6287768b91afef3a2feb4d9d2e27606d","outcomeBytes":154,"outcomeSha256":"1a074b69790ebf713dcd65a76ddc630a75ce996ae01404a552583a2211b5e125","summary":{"outcome":"failure","source":"transport","code":"envelopeTooLarge","requestedBytes":"657381","limitBytes":"657380","proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":52872,"processMaxRssKiBAfterInit":54084,"processMaxRssKiBBefore":93628,"processMaxRssKiBAfter":93628,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18},{"sampleIndex":4,"initSyncElapsedNs":"1239389","elapsedNs":"40381","requestBytes":657381,"requestSha256":"8569362aca144e7f9b4f83af9f9b91ef6287768b91afef3a2feb4d9d2e27606d","outcomeBytes":154,"outcomeSha256":"1a074b69790ebf713dcd65a76ddc630a75ce996ae01404a552583a2211b5e125","summary":{"outcome":"failure","source":"transport","code":"envelopeTooLarge","requestedBytes":"657381","limitBytes":"657380","proportionalFieldsPresent":false},"processMaxRssKiBBeforeInit":50884,"processMaxRssKiBAfterInit":52224,"processMaxRssKiBBefore":96724,"processMaxRssKiBAfter":96724,"wasmMemoryBytesAfterInit":1114112,"wasmMemoryBytesBefore":1179648,"wasmMemoryBytesAfter":1179648,"wasmMemoryPagesAfterInit":17,"wasmMemoryPagesBefore":18,"wasmMemoryPagesAfter":18}]}]} diff --git a/packages/colors/compiler.d.ts b/packages/colors/compiler.d.ts index 7280b5ff..8c82f65d 100644 --- a/packages/colors/compiler.d.ts +++ b/packages/colors/compiler.d.ts @@ -1,6 +1,9 @@ /// -import type { Wcag22FeasibilityOutcomeV1 } from "./compiler/labcolors_compiler.js"; +import type { + Wcag22ExplicitSelectionOutcomeV1, + Wcag22FeasibilityOutcomeV1, +} from "./compiler/labcolors_compiler.js"; export { default, @@ -20,3 +23,35 @@ export type { Wcag22FeasibilityOutcomeV1, Wcag22FeasibilityRequestV1, } from "./compiler/labcolors_compiler.js"; + +export type { + Wcag22ExplicitCandidateV1, + Wcag22ExplicitDecisionV1, + Wcag22ExplicitEvaluatedV1, + Wcag22ExplicitEvaluationProofV1, + Wcag22ExplicitFinalVerificationV1, + Wcag22ExplicitInvalidSelectionRequestV1, + Wcag22ExplicitNoSelectionV1, + Wcag22ExplicitNotEvaluatedV1, + Wcag22ExplicitPolicyBindingV1, + Wcag22ExplicitSelectedV1, + Wcag22ExplicitSelectionErrorV1, + Wcag22ExplicitSelectionIntegrityViolationV1, + Wcag22ExplicitSelectionOperationErrorV1, + Wcag22ExplicitSelectionOutcomeV1, + Wcag22ExplicitSelectionPolicyV1, + Wcag22ExplicitSelectionRequestV1, + Wcag22ExplicitSelectionResultV1, + Wcag22ExplicitSelectionTransportErrorV1, +} from "./compiler/labcolors_compiler.js"; + +/** Exact derived atomic-operation ceiling, available after WASM init. */ +export declare function wcag22ExplicitSelectionMaxBytes(): number; + +/** + * Evaluate one strict atomic `wcag22-explicit-selection-v1` UTF-8 envelope + * with the same hostile-input preflight as the feasibility entry. + */ +export declare function evaluateWcag22ExplicitSelection( + request: Uint8Array, +): Wcag22ExplicitSelectionOutcomeV1; diff --git a/packages/colors/compiler.js b/packages/colors/compiler.js index 03d7cc62..8c1c2a89 100644 --- a/packages/colors/compiler.js +++ b/packages/colors/compiler.js @@ -69,3 +69,51 @@ export function evaluateWcag22Feasibility(request) { } return evaluateWcag22FeasibilityRawV1(canonicalRequest); } + +// ── Атомарная операция `wcag22-explicit-selection-v1` (#296-C3) ────────────── + +import { + evaluateWcag22ExplicitSelectionV1 as evaluateWcag22ExplicitSelectionRawV1, + wcag22ExplicitSelectionEnvelopeTooLargeV1, + wcag22ExplicitSelectionMaxRequestBytesV1, +} from "./compiler/labcolors_compiler.js"; + +/** Exact derived atomic-operation ceiling, available after WASM init. */ +export function wcag22ExplicitSelectionMaxBytes() { + return wcag22ExplicitSelectionMaxRequestBytesV1(); +} + +/** + * Evaluate one strict atomic `wcag22-explicit-selection-v1` UTF-8 envelope. + * + * Тот же hostile-preflight, что и у feasibility: неверный тип и oversize + * отклоняются до избегаемой ABI-копии, detached/подменённые view — до WASM; + * Rust повторяет авторитетную проверку конверта. + * + * @param {Uint8Array} request + */ +export function evaluateWcag22ExplicitSelection(request) { + if (!hasUint8ArrayBrand(request)) { + throw new TypeError( + "evaluateWcag22ExplicitSelection request must be a Uint8Array", + ); + } + const snapshotBytes = typedArrayByteLength.call(request); + let canonicalRequest; + try { + canonicalRequest = new Uint8ArrayConstructor( + typedArrayBuffer.call(request), + typedArrayByteOffset.call(request), + snapshotBytes, + ); + } catch { + throw new TypeError( + "evaluateWcag22ExplicitSelection request must be a live Uint8Array", + ); + } + const requestedBytes = typedArrayByteLength.call(canonicalRequest); + if (requestedBytes > wcag22ExplicitSelectionMaxBytes()) { + return wcag22ExplicitSelectionEnvelopeTooLargeV1(BigInt(requestedBytes)); + } + return evaluateWcag22ExplicitSelectionRawV1(canonicalRequest); +} diff --git a/packages/colors/test/compiler-boundary.test.mjs b/packages/colors/test/compiler-boundary.test.mjs index f5bd0972..44a5f76a 100644 --- a/packages/colors/test/compiler-boundary.test.mjs +++ b/packages/colors/test/compiler-boundary.test.mjs @@ -55,7 +55,7 @@ test("runtime and compiler have disjoint normal dependency graphs", () => { assert.doesNotMatch(runtimeManifest, /labcolors-protocol/u); assert.match( compilerManifest, - /labcolors-protocol = \{ path = "\.\.\/labcolors-protocol" \}/u, + /labcolors-protocol = \{ path = "\.\.\/labcolors-protocol", features = \["wcag22-explicit-selection"\] \}/u, ); assert.doesNotMatch(compilerManifest, /labcolors-wasm|labcolors-core/u); }); diff --git a/packages/colors/test/release-contract.test.mjs b/packages/colors/test/release-contract.test.mjs index 299a6836..67c06f36 100644 --- a/packages/colors/test/release-contract.test.mjs +++ b/packages/colors/test/release-contract.test.mjs @@ -124,7 +124,10 @@ test("every workspace package inherits the declared MSRV", () => { test("runtime and compiler resolve disjoint Core capability graphs", () => { const isolatedCoreEdge = /labcolors-core = \{ path = "\.\.\/labcolors-core", default-features = false \}/u; - const protocolEdge = /labcolors-protocol = \{ path = "\.\.\/labcolors-protocol" \}/u; + // #296-C3: каждый protocol-потребитель публикует атомарную операцию через + // одну non-default фичу; голых protocol-рёбер в воркспейсе больше нет. + const protocolEdge = + /labcolors-protocol = \{ path = "\.\.\/labcolors-protocol", features = \["wcag22-explicit-selection"\] \}/u; const protocolManifest = read("crates", "labcolors-protocol", "Cargo.toml"); const wasmManifest = read("crates", "labcolors-wasm", "Cargo.toml"); const compilerManifest = read("crates", "labcolors-compiler", "Cargo.toml"); @@ -142,16 +145,11 @@ test("runtime and compiler resolve disjoint Core capability graphs", () => { assert.match(ffiManifest, isolatedCoreEdge); assert.match(ffiManifest, protocolEdge); assert.doesNotMatch(ffiManifest, /features = \["wcag22-feasibility"\]/u); - assert.doesNotMatch(ffiManifest, /wcag22-explicit-selection/u); - // #296-C2: только нативный conformance-генератор включает explicit-операцию - // protocol; прямое Core-ребро conformance остаётся без capability-фич, а - // публикуемые adapters (compiler, ffi) — feasibility-only до #296-C3. assert.match(conformanceManifest, isolatedCoreEdge); - assert.match( - conformanceManifest, - /labcolors-protocol = \{ path = "\.\.\/labcolors-protocol", features = \["wcag22-explicit-selection"\] \}/u, - ); + assert.match(conformanceManifest, protocolEdge); assert.doesNotMatch(conformanceManifest, /features = \["wcag22-feasibility"\]/u); + // Прямые Core-рёбра потребителей не несут capability-фич: explicit-домен + // приходит только через protocol-фичу. assert.doesNotMatch( conformanceManifest, /labcolors-core\/wcag22-explicit-feasibility/u, @@ -203,7 +201,11 @@ test("runtime and compiler resolve disjoint Core capability graphs", () => { ); assert.match( projection, - /'labcolors-core feature "wcag22-explicit-feasibility"' in feature_tree/u, + /'labcolors-core feature "wcag22-explicit-feasibility"' not in feature_tree/u, + ); + assert.match( + projection, + /'labcolors-protocol feature "wcag22-explicit-selection"'\n\s+not in feature_tree/u, ); assert.doesNotMatch(projection, /for consumer in labcolors-/u); }); @@ -1100,9 +1102,17 @@ test("release evidence carries the versioned WCAG22 feasibility operation", () = /JSON\.stringify\(evaluateWcag22Feasibility\(feasibilityRequest\)\)[\s\S]*?feasibilityFixture\.outcomeJson/u, ); assert.equal( - verifier.match(/compilerSmokeSource\(feasibilityFixture\)/gu)?.length, + verifier.match( + /compilerSmokeSource\(feasibilityFixture, explicitSelectionFixture\)/gu, + )?.length, 4, - "clean-install, role-isolation and Node-floor smokes must execute the same canonical fixture", + "clean-install, role-isolation and Node-floor smokes must execute the same canonical fixtures", + ); + assert.match(verifier, /evaluateWcag22ExplicitSelection/u); + assert.match(verifier, /wcag22ExplicitSelectionMaxBytes/u); + assert.match( + verifier, + /JSON\.stringify\(evaluateWcag22ExplicitSelection\(explicitSelectionRequest\)\)[\s\S]*?explicitSelectionFixture\.outcomeJson/u, ); const compilerSmoke = verifier.slice( verifier.indexOf("function compilerSmokeSource"), @@ -1124,7 +1134,7 @@ test("release evidence carries the versioned WCAG22 feasibility operation", () = test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => { const bench = join(root, "packages", "colors", "bench"); const paths = Object.fromEntries( - [1, 2, 3, 4, 5].map((version) => [ + [1, 2, 3, 4, 5, 6].map((version) => [ `v${version}`, join(bench, `wasm-size-budget-v${version}.json`), ]), @@ -1138,6 +1148,7 @@ test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => v3: "d7937612e4c33574a8af28845bb1dd30cca86fc39fc0206cac4c377de77fec15", v4: "c34fc10404dc7057a53a28592d18342078b5cd0e5dcaa888db482abf3f5fb23c", v5: "e4b53a2eb976a8c66827a559cb81232e359b734dbfb14725da215cb496ff5d59", + v6: "761af6050031169dac7eafdfadb2db9bbb2023b96ed5ba9d3c5dc966ffeafb32", }; const documents = {}; for (const version of Object.keys(paths)) { @@ -1148,7 +1159,7 @@ test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => if (version !== "v1") assert.equal(bytes.toString("utf8"), canonicalJson(value)); } - const { v1, v2, v3, v4, v5 } = documents; + const { v1, v2, v3, v4, v5, v6 } = documents; assert.equal(v1.budgetId, "labcolors-wasm-raw-issue-284-v1"); assert.equal(v2.budgetId, "labcolors-wasm-raw-issue-295-v2"); assert.equal(v3.budgetId, "labcolors-wasm-raw-issue-296-v3"); @@ -1202,11 +1213,38 @@ test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => assert.ok(v5.roles.runtime.policy.maxRawBytes <= v1.policy.maxRawBytes); assert.ok(v5.roles.runtime.policy.maxRawBytes <= v4.policy.maxRawBytes); + // V6 (#296-C3): compiler-роль публикует атомарную операцию — рост размера + // зафиксирован новым точным измерением; runtime-измерение байт-идентично C1. + assert.equal(v6.schemaVersion, 5); + assert.equal(v6.budgetId, "labcolors-wasm-roles-issue-296-c3-v6"); + assert.deepEqual(v6.predecessor, { + path: "packages/colors/bench/wasm-size-budget-v5.json", + fileSha256: expectedHashes.v5, + }); + assert.deepEqual(v6.toolchainSource, v5.toolchainSource); + assert.deepEqual(v6.buildRecipes, v5.buildRecipes); + assert.deepEqual(v6.roles.runtime, v5.roles.runtime); + assert.deepEqual(v6.roles.compiler.measurement, { + issue: 296, + slice: "C3", + measurementPlatform: "linux-x64", + rawBytes: 229658, + sha256: "34e2a561862ee06d52d1104f8ba60ccf9967e2e4fd09803d4e75e1966074bc8d", + }); + assert.equal( + v6.roles.compiler.policy.derivation, + "exact-accepted-issue-296-slice-c3-compiler-measurement", + ); + assert.equal(v6.roles.runtime.policy.maxRawBytes, v6.roles.runtime.measurement.rawBytes); + assert.equal(v6.roles.compiler.policy.maxRawBytes, v6.roles.compiler.measurement.rawBytes); + assert.ok(v6.roles.runtime.policy.maxRawBytes <= v1.policy.maxRawBytes); + assert.ok(v6.roles.runtime.policy.maxRawBytes <= v4.policy.maxRawBytes); + const checker = await import( new URL("../../../scripts/check-wasm-size-budget.mjs", import.meta.url) ); - assert.equal(checker.DEFAULT_BUDGET, paths.v5); - for (const version of [1, 2, 3, 4, 5]) { + assert.equal(checker.DEFAULT_BUDGET, paths.v6); + for (const version of [1, 2, 3, 4, 5, 6]) { assert.equal(checker[`V${version}_FILE_SHA256`], expectedHashes[`v${version}`]); } assert.equal(checker.V1_RECIPE_SHA256, v5.buildRecipes.runtime.recipeSha256); @@ -1222,7 +1260,7 @@ test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => "bench", "wcag22-feasibility-boundary.bench.mjs", ); - assert.match(wholeCallSource, /wasmBudgetPath = resolve\(here, "wasm-size-budget-v5\.json"\)/u); + assert.match(wholeCallSource, /wasmBudgetPath = resolve\(here, "wasm-size-budget-v6\.json"\)/u); const ci = read(".github", "workflows", "ci.yml"); assert.match(ci, /name: enforce measured WASM role budgets/u); @@ -1255,7 +1293,7 @@ test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => const compilerBytes = Buffer.alloc(17); runtimeBytes.set([0x00, 0x61, 0x73, 0x6d]); compilerBytes.set([0x00, 0x61, 0x73, 0x6d]); - const fixture = structuredClone(v5); + const fixture = structuredClone(v6); for (const [role, bytes] of [["runtime", runtimeBytes], ["compiler", compilerBytes]]) { fixture.roles[role].measurement.rawBytes = bytes.length; fixture.roles[role].measurement.sha256 = sha256(bytes); @@ -1344,8 +1382,8 @@ test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => writeFileSync( fixtureBudgetPath, canonicalJson(fixture).replace( - ' "schemaVersion": 4,\n', - ' "schemaVersion": 4,\n "schemaVersion": 4,\n', + ' "schemaVersion": 5,\n', + ' "schemaVersion": 5,\n "schemaVersion": 5,\n', ), ); assert.throws(run, /canonical JSON/u, "duplicate JSON fields must fail"); @@ -1385,8 +1423,8 @@ test("WCAG22 WASM role budgets are exact, append-only, and acyclic", async () => Buffer.from(compilerBytes).fill(1, compilerBytes.length - 1), ); assert.throws( - () => checker.parseBudgetDocument(Buffer.from(canonicalJson(coordinatedMutation)), paths.v5), - /immutable v5 file SHA-256 mismatch/u, + () => checker.parseBudgetDocument(Buffer.from(canonicalJson(coordinatedMutation)), paths.v6), + /current v6 file SHA-256 mismatch/u, "coordinated artifact and document drift must still fail the default identity", ); } finally { diff --git a/packages/colors/test/wcag22-explicit-selection.test.mjs b/packages/colors/test/wcag22-explicit-selection.test.mjs new file mode 100644 index 00000000..8e182b84 --- /dev/null +++ b/packages/colors/test/wcag22-explicit-selection.test.mjs @@ -0,0 +1,82 @@ +// Публикация атомарной операции `wcag22-explicit-selection-v1` в compiler-роли +// (#296-C3): собранный WASM обязан повекторно воспроизвести закоммиченное +// conformance-семейство байт-в-байт, а host-обвязка — повторять hostile-законы +// feasibility-входа (preflight типа/oversize до избегаемой ABI-копии). + +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { test } from "node:test"; +import { fileURLToPath } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); +const root = resolve(here, "../../.."); + +const compilerModule = await import("../compiler.js"); +const { + default: init, + evaluateWcag22ExplicitSelection, + wcag22ExplicitSelectionMaxBytes, +} = compilerModule; +const wasmBytes = readFileSync( + join(root, "packages/colors/compiler/labcolors_compiler_bg.wasm"), +); +await init({ module_or_path: wasmBytes }); + +const family = JSON.parse( + readFileSync( + join(root, "conformance/vectors/wcag22-explicit-selection.json"), + "utf8", + ), +); + +test("built compiler replays the committed explicit-selection family byte-for-byte", () => { + assert.equal(family.length, 15, "committed corpus cardinality drifted"); + for (const vector of family) { + const outcome = evaluateWcag22ExplicitSelection( + new TextEncoder().encode(vector.requestJson), + ); + assert.equal( + JSON.stringify(outcome), + vector.outcomeJson, + `${vector.caseId}: compiler outcome bytes drifted from the committed corpus`, + ); + } +}); + +test("atomic ceiling is derived from WASM and matches the protocol constant", () => { + assert.equal(wcag22ExplicitSelectionMaxBytes(), 3_889_322); +}); + +test("oversize envelope fails closed before the avoidable WASM copy", () => { + const oversized = new Uint8Array(wcag22ExplicitSelectionMaxBytes() + 1); + const outcome = evaluateWcag22ExplicitSelection(oversized); + assert.equal(outcome.outcome, "failure"); + assert.equal(outcome.error.source, "transport"); + assert.equal(outcome.error.error.code, "envelopeTooLarge"); + assert.equal( + outcome.error.error.requestedBytes, + String(wcag22ExplicitSelectionMaxBytes() + 1), + ); +}); + +test("non-Uint8Array and detached views are rejected before WASM", () => { + for (const hostile of [null, "bytes", [1, 2, 3], new Float32Array(4)]) { + assert.throws( + () => evaluateWcag22ExplicitSelection(hostile), + TypeError, + String(hostile), + ); + } + const buffer = new ArrayBuffer(8); + const view = new Uint8Array(buffer); + structuredClone(buffer, { transfer: [buffer] }); + assert.throws(() => evaluateWcag22ExplicitSelection(view), TypeError); +}); + +test("published surface keeps the neutral feasibility operation intact", () => { + // Обе операции живут в одной compiler-роли; публикация атомарной не + // сдвигает нейтральную: тот же модуль отдаёт обе точки входа. + assert.equal(typeof compilerModule.evaluateWcag22Feasibility, "function"); + assert.equal(typeof compilerModule.wcag22FeasibilityMaxBytes, "function"); +}); diff --git a/packages/colors/test/wcag22-feasibility-boundary.test.mjs b/packages/colors/test/wcag22-feasibility-boundary.test.mjs index d3c3606d..78070d5f 100644 --- a/packages/colors/test/wcag22-feasibility-boundary.test.mjs +++ b/packages/colors/test/wcag22-feasibility-boundary.test.mjs @@ -27,7 +27,7 @@ const conformanceManifestPath = resolve(root, "conformance/vectors/manifest.json const packageManifestPath = resolve(root, "packages/colors/package.json"); const compilerEntryPath = resolve(root, "packages/colors/compiler.js"); const wasmGluePath = resolve(root, "packages/colors/compiler/labcolors_compiler.js"); -const wasmBudgetPath = resolve(root, "packages/colors/bench/wasm-size-budget-v5.json"); +const wasmBudgetPath = resolve(root, "packages/colors/bench/wasm-size-budget-v6.json"); const coreBytes = readFileSync(corePath); const core = JSON.parse(coreBytes); const toolchain = JSON.parse(readFileSync(toolchainPath)); @@ -241,7 +241,7 @@ function fixture() { }, }, wasmBudget: { - path: "packages/colors/bench/wasm-size-budget-v5.json", + path: "packages/colors/bench/wasm-size-budget-v6.json", schemaVersion: wasmBudget.schemaVersion, budgetId: wasmBudget.budgetId, fileSha256: sha256(wasmBudgetBytes), @@ -276,6 +276,10 @@ test("whole-call evidence history is exact and deterministic", () => { root, "packages/colors/bench/wcag22-feasibility-wasm-boundary-v4.json", )); + const v5Bytes = readFileSync(resolve( + root, + "packages/colors/bench/wcag22-feasibility-wasm-boundary-v5.json", + )); assert.equal( sha256(v1Bytes), "8281f372cf635174fa3cedf828a96b48a023c413f43245cfc7001d9b83ff1790", @@ -292,14 +296,20 @@ test("whole-call evidence history is exact and deterministic", () => { sha256(v4Bytes), "34fcc24d74c1c0b877c04457799d5d67b8947915199a14202f737353bfff4257", ); + assert.equal( + sha256(v5Bytes), + "97cb5478db091e8c3aac4a22f5ea801197375a1d158e6e39de2e237f991e88a7", + ); const v1 = JSON.parse(v1Bytes); const v2 = JSON.parse(v2Bytes); const v3 = JSON.parse(v3Bytes); const v4 = JSON.parse(v4Bytes); + const v5 = JSON.parse(v5Bytes); assert.equal(v1.artifactId, "wcag22-feasibility-wasm-whole-call-v1"); assert.equal(v2.artifactId, "wcag22-feasibility-wasm-whole-call-v2"); assert.equal(v3.artifactId, "wcag22-feasibility-wasm-whole-call-v3"); assert.equal(v4.artifactId, "wcag22-feasibility-wasm-whole-call-v4"); + assert.equal(v5.artifactId, "wcag22-feasibility-wasm-whole-call-v5"); // V4 перезаписан после pack-6/admission-V5: связывает точную новую истину, // компилерный WASM байт-в-байт равен ратчету C1. assert.deepEqual(v4.bindings.coreAdmission, { @@ -315,6 +325,19 @@ test("whole-call evidence history is exact and deterministic", () => { sha256: "3a552ce43ada7d0b10e90a23b4a7e50a4ecad77a446374b98ca8ee6b5c6a2a45", }); assert.equal(v4.bindings.packOracle.packVersion, "6.0.0"); + // V5 (#296-C3): compiler публикует атомарную операцию — новый exact WASM, + // бюджет V6 и та же admission V5; deterministic-проекция не меняется. + assert.deepEqual(v5.bindings.coreAdmission, v4.bindings.coreAdmission); + assert.deepEqual(v5.bindings.wasm, { + path: "packages/colors/compiler/labcolors_compiler_bg.wasm", + bytes: 229658, + sha256: "34e2a561862ee06d52d1104f8ba60ccf9967e2e4fd09803d4e75e1966074bc8d", + }); + assert.equal( + v5.bindings.wasmBudget.budgetId, + "labcolors-wasm-roles-issue-296-c3-v6", + ); + assert.equal(v5.bindings.packOracle.packDigest, "129158d2"); assert.deepEqual(v2.bindings.coreAdmission, { path: "crates/labcolors-core/contracts/wcag22-feasibility-benchmark-v3.json", schemaVersion: 1, @@ -358,6 +381,7 @@ test("whole-call evidence history is exact and deterministic", () => { assert.deepEqual(deterministicProjection(v2), deterministicProjection(v1)); assert.deepEqual(deterministicProjection(v3), deterministicProjection(v2)); assert.deepEqual(deterministicProjection(v4), deterministicProjection(v3)); + assert.deepEqual(deterministicProjection(v5), deterministicProjection(v4)); }); test("canonical whole-call artifact schema accepts all immutable scenarios", () => { @@ -437,7 +461,7 @@ test("whole-call checker mutation-kills missing evidence and inflated claims", ( test("CI verifies committed whole-call evidence before the size gate", () => { const ci = ciSource; const harness = "node bench/wcag22-feasibility-boundary.bench.mjs"; - const evidence = "bench/wcag22-feasibility-wasm-boundary-v4.json"; + const evidence = "bench/wcag22-feasibility-wasm-boundary-v5.json"; const verify = `--verify ${evidence}`; const fingerprint = "name: independently fingerprint both execution-role WASM artifacts"; const upload = @@ -448,10 +472,10 @@ test("CI verifies committed whole-call evidence before the size gate", () => { const fingerprintIndex = ci.indexOf(fingerprint, verifyIndex); const uploadIndex = ci.indexOf(upload, verifyIndex); const sizeGateIndex = ci.indexOf(sizeGate); - assert.match(ci, /verify committed #296-C2 canonical whole-call compiler evidence/u); + assert.match(ci, /verify committed #296-C3 canonical whole-call compiler evidence/u); assert.match( ci, - /name: upload exact #296-C2 whole-call evidence/u, + /name: upload exact #296-C3 whole-call evidence/u, ); assert.ok(harnessIndex >= 0, "the compiler-entry harness must run in CI"); assert.ok(verifyIndex > harnessIndex, "CI must verify the committed evidence"); @@ -466,7 +490,7 @@ test("CI verifies committed whole-call evidence before the size gate", () => { ); assert.match( ci, - /path: \|[\s\S]*?packages\/colors\/bench\/wcag22-feasibility-wasm-boundary-v4\.json[\s\S]*?packages\/colors\/pkg\/labcolors_bg\.wasm[\s\S]*?packages\/colors\/compiler\/labcolors_compiler_bg\.wasm/u, + /path: \|[\s\S]*?packages\/colors\/bench\/wcag22-feasibility-wasm-boundary-v5\.json[\s\S]*?packages\/colors\/pkg\/labcolors_bg\.wasm[\s\S]*?packages\/colors\/compiler\/labcolors_compiler_bg\.wasm/u, ); assert.doesNotMatch( ci, diff --git a/packages/colors/test/wcag22-feasibility.test.mjs b/packages/colors/test/wcag22-feasibility.test.mjs index 111847dc..2a65296f 100644 --- a/packages/colors/test/wcag22-feasibility.test.mjs +++ b/packages/colors/test/wcag22-feasibility.test.mjs @@ -23,6 +23,7 @@ async function importCompilerWithInstrumentedWasm(t) { join(fixture, "compiler/labcolors_compiler.js"), ` globalThis.__labcolorsFeasibilityCalls = { evaluate: [], max: [], oversize: [] }; +globalThis.__labcolorsExplicitCalls = { evaluate: [], max: [], oversize: [] }; let initialized = false; export default async function init() { initialized = true; } export function initSync() { initialized = true; } @@ -53,6 +54,33 @@ export function wcag22FeasibilityEnvelopeTooLargeV1(requestedBytes) { }, }; } + +export function wcag22ExplicitSelectionMaxRequestBytesV1() { + if (!initialized) throw new Error("WASM not initialized"); + globalThis.__labcolorsExplicitCalls.max.push(true); + return 3889322; +} +export function evaluateWcag22ExplicitSelectionV1(request) { + if (!initialized) throw new Error("WASM not initialized"); + globalThis.__labcolorsExplicitCalls.evaluate.push(request); + return { schemaVersion: 1, outcome: "failure", error: { source: "incompatibleCoreContract" } }; +} +export function wcag22ExplicitSelectionEnvelopeTooLargeV1(requestedBytes) { + if (!initialized) throw new Error("WASM not initialized"); + globalThis.__labcolorsExplicitCalls.oversize.push(requestedBytes); + return { + schemaVersion: 1, + outcome: "failure", + error: { + source: "transport", + error: { + code: "envelopeTooLarge", + requestedBytes: requestedBytes.toString(), + limitBytes: "3889322", + }, + }, + }; +} `, ); return import(`${pathToFileURL(join(fixture, "compiler.js")).href}?case=${Date.now()}`); diff --git a/scripts/check-wasm-size-budget.mjs b/scripts/check-wasm-size-budget.mjs index 68ec4f16..84fcf813 100644 --- a/scripts/check-wasm-size-budget.mjs +++ b/scripts/check-wasm-size-budget.mjs @@ -12,10 +12,11 @@ const V1_PATH = resolve(REPO_ROOT, "packages/colors/bench/wasm-size-budget-v1.js const V2_PATH = resolve(REPO_ROOT, "packages/colors/bench/wasm-size-budget-v2.json"); const V3_PATH = resolve(REPO_ROOT, "packages/colors/bench/wasm-size-budget-v3.json"); const V4_PATH = resolve(REPO_ROOT, "packages/colors/bench/wasm-size-budget-v4.json"); +const V5_PATH = resolve(REPO_ROOT, "packages/colors/bench/wasm-size-budget-v5.json"); export const DEFAULT_BUDGET = resolve( REPO_ROOT, - "packages/colors/bench/wasm-size-budget-v5.json", + "packages/colors/bench/wasm-size-budget-v6.json", ); export const V1_FILE_SHA256 = "4f7340fc8cfd0ccb97377c385f2f8d8e7a9ef2c5ba96177f518c5d07de2825e1"; @@ -29,10 +30,13 @@ export const V4_FILE_SHA256 = "c34fc10404dc7057a53a28592d18342078b5cd0e5dcaa888db482abf3f5fb23c"; export const V5_FILE_SHA256 = "e4b53a2eb976a8c66827a559cb81232e359b734dbfb14725da215cb496ff5d59"; +export const V6_FILE_SHA256 = + "761af6050031169dac7eafdfadb2db9bbb2023b96ed5ba9d3c5dc966ffeafb32"; const V1_REPOSITORY_PATH = "packages/colors/bench/wasm-size-budget-v1.json"; -const V4_REPOSITORY_PATH = "packages/colors/bench/wasm-size-budget-v4.json"; +const V5_REPOSITORY_PATH = "packages/colors/bench/wasm-size-budget-v5.json"; const V5_BUDGET_ID = "labcolors-wasm-roles-issue-296-c1-v5"; +const V6_BUDGET_ID = "labcolors-wasm-roles-issue-296-c3-v6"; const ROLE_ORDER = ["runtime", "compiler"]; const ROLE_SPECS = { runtime: { @@ -41,13 +45,17 @@ const ROLE_SPECS = { "CARGO_ENCODED_RUSTFLAGS= wasm-pack build crates/labcolors-wasm --release --target web --out-dir ../../packages/colors/pkg --out-name labcolors --locked", recipeSha256: V1_RECIPE_SHA256, derivation: "exact-accepted-issue-296-slice-c1-runtime-measurement", + measurementSlice: "C1", }, compiler: { artifact: "packages/colors/compiler/labcolors_compiler_bg.wasm", command: "CARGO_ENCODED_RUSTFLAGS= wasm-pack build crates/labcolors-compiler --release --target web --out-dir ../../packages/colors/compiler --out-name labcolors_compiler --locked", recipeSha256: "ce53cea5f579c512a6d2f0c3348f250ac0a5e03206de55e7979c8eae1403be8f", - derivation: "exact-accepted-issue-296-slice-c1-compiler-first-admission", + // C3 публикует атомарную операцию в compiler-роли: рост размера — это + // добавленная capability, зафиксированная новым точным измерением. + derivation: "exact-accepted-issue-296-slice-c3-compiler-measurement", + measurementSlice: "C3", }, }; @@ -144,7 +152,11 @@ function verifyImmutableHistory() { } if (label === "v4") v4 = value; } - return { v1, v4 }; + const v5 = readImmutableJson(V5_PATH, V5_FILE_SHA256, "v5"); + if (v5?.schemaVersion !== 4 || v5?.budgetId !== V5_BUDGET_ID) { + fail("immutable v5 budget identity drifted"); + } + return { v1, v4, v5 }; } function validateBudgetValue(budget) { @@ -160,15 +172,15 @@ function validateBudgetValue(budget) { ], "budget", ); - if (budget.schemaVersion !== 4) fail("supported schemaVersion is exactly 4"); - if (budget.budgetId !== V5_BUDGET_ID) fail(`budgetId must be ${V5_BUDGET_ID}`); + if (budget.schemaVersion !== 5) fail("supported schemaVersion is exactly 5"); + if (budget.budgetId !== V6_BUDGET_ID) fail(`budgetId must be ${V6_BUDGET_ID}`); exactKeys(budget.predecessor, ["path", "fileSha256"], "predecessor"); if ( - budget.predecessor.path !== V4_REPOSITORY_PATH || - budget.predecessor.fileSha256 !== V4_FILE_SHA256 + budget.predecessor.path !== V5_REPOSITORY_PATH || + budget.predecessor.fileSha256 !== V5_FILE_SHA256 ) { - fail("predecessor must bind the immutable v4 document"); + fail("predecessor must bind the immutable v5 document"); } exactKeys(budget.toolchainSource, ["path", "fileSha256"], "toolchainSource"); @@ -207,8 +219,8 @@ function validateBudgetValue(budget) { ["issue", "slice", "measurementPlatform", "rawBytes", "sha256"], `roles.${role}.measurement`, ); - if (record.measurement.issue !== 296 || record.measurement.slice !== "C1") { - fail(`roles.${role}.measurement must cite Issue #296 Slice C1`); + if (record.measurement.issue !== 296 || record.measurement.slice !== spec.measurementSlice) { + fail(`roles.${role}.measurement must cite Issue #296 Slice ${spec.measurementSlice}`); } if (record.measurement.measurementPlatform !== "linux-x64") { fail(`roles.${role}.measurement must use canonical linux-x64`); @@ -252,10 +264,10 @@ export function parseBudgetDocument(bytes, budgetPath) { validateBudgetValue(budget); if (resolve(budgetPath) === DEFAULT_BUDGET) { const actualFileSha256 = sha256(document); - if (actualFileSha256 !== V5_FILE_SHA256) { + if (actualFileSha256 !== V6_FILE_SHA256) { fail( - `immutable v5 file SHA-256 mismatch: ` + - `expected=${V5_FILE_SHA256} actual=${actualFileSha256}`, + `current v6 file SHA-256 mismatch: ` + + `expected=${V6_FILE_SHA256} actual=${actualFileSha256}`, ); } } diff --git a/scripts/prepare-npm-package.mjs b/scripts/prepare-npm-package.mjs index 22243364..7aa09070 100644 --- a/scripts/prepare-npm-package.mjs +++ b/scripts/prepare-npm-package.mjs @@ -23,8 +23,8 @@ const WCAG22_EVIDENCE_FILES = [ const CONFORMANCE_DIR = resolve(REPO_ROOT, "conformance/vectors"); // Полный состав пака 6.0.0: восемь семейств. В npm-тарболл эти файлы НЕ // копируются — байты хешируются из репозитория в build-metadata provenance -// (packDigest/familySetSha256), поэтому native-only семейство -// wcag22-explicit-selection не расширяет публикуемую поверхность. +// (packDigest/familySetSha256); семейство wcag22-explicit-selection в +// тарболл не входит — публикуемая поверхность это код адаптеров. const CONFORMANCE_FILES = [ "contrasts.json", "ladders.json", diff --git a/scripts/verify-package-release.mjs b/scripts/verify-package-release.mjs index 7c791041..cd7ac7ca 100644 --- a/scripts/verify-package-release.mjs +++ b/scripts/verify-package-release.mjs @@ -32,7 +32,8 @@ const WCAG22_EVIDENCE_FILES = [ ]; // Полный состав пака 6.0.0. Верификатор читает байты из репозитория (не из // тарболла) и пересчитывает packDigest над всеми восемью семействами; -// wcag22-explicit-selection остаётся native-only и в пакет не попадает. +// семейство wcag22-explicit-selection в пакет не попадает (операцию +// потребитель получает кодом обоих адаптеров, а не векторами). const CONFORMANCE_FAMILY_FILES = [ "contrasts.json", "ladders.json", @@ -1256,6 +1257,57 @@ async function wcag22FeasibilitySmokeFixture() { }; } +async function wcag22ExplicitSelectionSmokeFixture() { + const family = await readJson( + resolve(CONFORMANCE_DIR, "wcag22-explicit-selection.json"), + ); + // Паритет с feasibility-фикстурой: семейство целиком проверяется на форму + // (каждый вектор — ровно caseId/requestJson/outcomeJson, оба JSON-парсятся, + // caseId уникальны), затем выбирается канонический selected-кейс. + if (!Array.isArray(family) || family.length === 0) { + fail("wcag22-explicit-selection family must be a non-empty vector array"); + } + const seen = new Set(); + for (const vector of family) { + if (vector === null || typeof vector !== "object" || Array.isArray(vector)) { + fail("wcag22-explicit-selection vector schema drifted: vector must be an object"); + } + const keys = Object.keys(vector); + if ( + keys.length !== 3 || + typeof vector.caseId !== "string" || + typeof vector.requestJson !== "string" || + typeof vector.outcomeJson !== "string" + ) { + fail(`wcag22-explicit-selection vector schema drifted: ${vector.caseId}`); + } + if (seen.has(vector.caseId)) { + fail(`wcag22-explicit-selection duplicate caseId: ${vector.caseId}`); + } + seen.add(vector.caseId); + try { + JSON.parse(vector.requestJson); + JSON.parse(vector.outcomeJson); + } catch (error) { + fail(`wcag22-explicit-selection ${vector.caseId} is not nested JSON: ${error.message}`); + } + } + const canonical = family.find( + (vector) => vector.caseId === "selected-declared-order-overrides-canonical", + ); + if (!canonical) { + fail("wcag22-explicit-selection smoke fixture is missing its canonical case"); + } + const outcome = JSON.parse(canonical.outcomeJson); + if (outcome?.outcome !== "success" || outcome?.result?.status !== "selected") { + fail("wcag22-explicit-selection canonical case must be a selected terminal"); + } + return { + requestJson: canonical.requestJson, + outcomeJson: canonical.outcomeJson, + }; +} + function runtimeSmokeSource() { return String.raw` import assert from "node:assert/strict"; @@ -1467,14 +1519,16 @@ for (const key of [ `; } -function compilerSmokeSource(feasibilityFixture) { +function compilerSmokeSource(feasibilityFixture, explicitSelectionFixture) { return String.raw` import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { createRequire } from "node:module"; import init, { + evaluateWcag22ExplicitSelection, evaluateWcag22Feasibility, + wcag22ExplicitSelectionMaxBytes, wcag22FeasibilityMaxBytes, } from "@labpics/colors/compiler"; @@ -1494,6 +1548,18 @@ assert.equal( JSON.stringify(evaluateWcag22Feasibility(feasibilityRequest)), feasibilityFixture.outcomeJson, ); + +const explicitSelectionFixture = ${JSON.stringify(explicitSelectionFixture)}; +const explicitSelectionRequest = new TextEncoder().encode( + explicitSelectionFixture.requestJson, +); +assert.ok( + explicitSelectionRequest.byteLength <= wcag22ExplicitSelectionMaxBytes(), +); +assert.equal( + JSON.stringify(evaluateWcag22ExplicitSelection(explicitSelectionRequest)), + explicitSelectionFixture.outcomeJson, +); `; } @@ -1520,8 +1586,12 @@ import init, { type Wcag22CriterionV1, } from "@labpics/colors"; import { + evaluateWcag22ExplicitSelection, evaluateWcag22Feasibility, + wcag22ExplicitSelectionMaxBytes, wcag22FeasibilityMaxBytes, + type Wcag22ExplicitSelectionOutcomeV1, + type Wcag22ExplicitSelectionRequestV1, type Wcag22FeasibilityOutcomeV1, type Wcag22FeasibilityRequestV1, } from "@labpics/colors/compiler"; @@ -1588,6 +1658,47 @@ const feasibilityOutcome: Wcag22FeasibilityOutcomeV1 = // @ts-expect-error byte API rejects strings. evaluateWcag22Feasibility(JSON.stringify(feasibilityRequest)); +const explicitSelectionRequest: Wcag22ExplicitSelectionRequestV1 = { + schemaVersion: 1, + domainId: "explicit-srgb8-set-v1", + resourceProfileId: "compile-v1", + candidates: [{ candidateId: "opaque-client-candidate", emitted: [255, 255, 255] }], + relations: [{ + relationId: "opaque-client-relation", + occurrenceId: "opaque-client-occurrence", + kind: "applicable", + criterion: "sc-1.4.3-text-default", + adjacent: [[0, 0, 0]], + }], + policy: { + policyKind: "first-feasible-in-declared-order-v1", + policyId: "opaque-client-policy", + orderedCandidateIds: ["opaque-client-candidate"], + }, +}; +const explicitSelectionBytes = new TextEncoder().encode( + JSON.stringify(explicitSelectionRequest), +); +const explicitSelectionCeiling: number = wcag22ExplicitSelectionMaxBytes(); +const explicitSelectionOutcome: Wcag22ExplicitSelectionOutcomeV1 = + evaluateWcag22ExplicitSelection(explicitSelectionBytes); +// @ts-expect-error byte API rejects strings. +evaluateWcag22ExplicitSelection(JSON.stringify(explicitSelectionRequest)); +// Закоммиченный вектор error-feasibility-priority-over-policy обязан быть +// представим в типовом контракте: explicit-код duplicateCandidateId живёт в +// feasibility-источнике операции. +const representableCommittedFailure: Wcag22ExplicitSelectionOutcomeV1 = { + schemaVersion: 1, + outcome: "failure", + error: { + source: "feasibility", + error: { + code: "invalidRequest", + details: { code: "duplicateCandidateId", candidateId: "twin" }, + }, + }, +}; + function assertNever(value: never): never { throw new Error("unreachable: " + String(value)); } @@ -1920,11 +2031,15 @@ async function verifyCleanConsumer( } const feasibilityFixture = await wcag22FeasibilitySmokeFixture(); + const explicitSelectionFixture = await wcag22ExplicitSelectionSmokeFixture(); const runtimePath = resolve(consumer, "runtime-smoke.mjs"); const compilerPath = resolve(consumer, "compiler-smoke.mjs"); const typesPath = resolve(consumer, "smoke.ts"); await writeFile(runtimePath, runtimeSmokeSource()); - await writeFile(compilerPath, compilerSmokeSource(feasibilityFixture)); + await writeFile( + compilerPath, + compilerSmokeSource(feasibilityFixture, explicitSelectionFixture), + ); await writeFile(typesPath, typeSmokeSource()); command(process.execPath, [runtimePath], consumer); @@ -1968,7 +2083,7 @@ async function verifyCleanConsumer( tarballPath, packageJson, "compiler", - compilerSmokeSource(feasibilityFixture), + compilerSmokeSource(feasibilityFixture, explicitSelectionFixture), ); } finally { await rm(consumer, { recursive: true, force: true }); @@ -2045,8 +2160,12 @@ export async function smokePackedPackage(tarballPath) { const runtimePath = resolve(consumer, "smoke.mjs"); const compilerPath = resolve(consumer, "compiler-smoke.mjs"); const feasibilityFixture = await wcag22FeasibilitySmokeFixture(); + const explicitSelectionFixture = await wcag22ExplicitSelectionSmokeFixture(); await writeFile(runtimePath, runtimeSmokeSource()); - await writeFile(compilerPath, compilerSmokeSource(feasibilityFixture)); + await writeFile( + compilerPath, + compilerSmokeSource(feasibilityFixture, explicitSelectionFixture), + ); command(process.execPath, [runtimePath], consumer); command(process.execPath, [compilerPath], consumer); } finally {