diff --git a/dag/extdeps/instrument/fs_box_v3_v4_v5_faq.dag b/dag/extdeps/instrument/fs_box_v3_v4_v5_faq.dag new file mode 100644 index 00000000000..c77552ea8b8 --- /dev/null +++ b/dag/extdeps/instrument/fs_box_v3_v4_v5_faq.dag @@ -0,0 +1,98 @@ +module extdeps.instrument.fs_box_v3_v4_v5_faq + +import std.types { Bool, List, NonEmptyStr } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import extdeps.external_authority { + ExternalAuthority, ExternalModelScope, ExternalSubjectRef, + CitedFigureStanding, CitedToAuthority, +} +import extdeps.uri { Uri, Https } + +// FS'S PUBLISHED FS BOX V3 & V4 & V5 FAQ (filename date 2026-08-24). This is a separately +// governed publication from the V4 data sheet. It is mostly service and account POLICY as FS +// printed it, which still belongs here as a citation of that publication: inventing a second +// "our policy" copy in workflow would nickname the same document. What THIS FLEET does with the +// terms is a product join (product.fs_box_published_claims). Free-BOX spend-tier campaign +// pricing was read and is not declared: it is a marketing offer with no executing consumer in +// this change. Account trial/freeze/buffer and the BOX Pro review window are cited because a live +// FS BOX account is a prerequisite of the programming intervention, and BOX Pro's writable-field +// split is what keeps byte 192 from being read out of this FAQ. +// +// THE FAQ DOES NOT GROUND LEG PROGRAMMING. It has no hits for disconnect, individually, breakout, +// QSFP56, EEPROM, byte 192, or 0x40. "Offline" is the internet requirement, not write recovery. + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "resource.fs.com/mall/resource/fs-box-v3v4v5-faq-20260824100858.pdf" + } +} + +type FsBoxV3V4V5Faq { + online_configuration_fields: List + box_pro_fields: List + box_pro_customization_review: NonEmptyStr + custom_compatible_brand_requires_engineering_approval: Bool + third_party_modules_supported: Bool + reconfiguration_not_supported: List + works_offline: Bool + operating_systems: List + browsers: List + trial_period: NonEmptyStr + trial_frozen_on_expiry: Bool + formal_account_buffer: NonEmptyStr + standing: CitedFigureStanding +} + +data fs_box_v3_v4_v5_faq: FsBoxV3V4V5Faq = FsBoxV3V4V5Faq { + online_configuration_fields: [ + "compatible brands" as NonEmptyStr, + "SN" as NonEmptyStr, + ], + box_pro_fields: [ + "Compatible Brands" as NonEmptyStr, + "Vendor names" as NonEmptyStr, + "P/N" as NonEmptyStr, + "S/N" as NonEmptyStr, + ], + box_pro_customization_review: "1-2 working days" as NonEmptyStr, + custom_compatible_brand_requires_engineering_approval: true, + third_party_modules_supported: false, + reconfiguration_not_supported: [ + "HPE-compatible SFP+/SFP28/XFP/QSFP+/QSFP28 transceivers" as NonEmptyStr, + "all 40G BiDi transceivers" as NonEmptyStr, + "GPON transceivers" as NonEmptyStr, + "25G SFP28 Loopback Modules" as NonEmptyStr, + ], + works_offline: false, + operating_systems: [ + "Windows 10 or later" as NonEmptyStr, + "macOS 10.13 or later" as NonEmptyStr, + "Android 5.0 or later" as NonEmptyStr, + "iOS 11.0 or later" as NonEmptyStr, + ], + browsers: [ + "Google Chrom(68 or newer)" as NonEmptyStr, + "Firefox" as NonEmptyStr, + ], + trial_period: "seven-day" as NonEmptyStr, + trial_frozen_on_expiry: true, + formal_account_buffer: "24-hour" as NonEmptyStr, + standing: CitedToAuthority { authority: extdeps_external_authority_anchor }, +} + +fn fs_box_faq_lists_box_pro_field(name: NonEmptyStr) -> Bool { + any(fs_box_v3_v4_v5_faq.box_pro_fields, f => (f as String) == (name as String)) +} + +data extdeps_model_scope: ExternalModelScope = ExternalModelScope { + subject: ExternalSubjectRef { + declaration: DeclarationRef { + module_path: "extdeps.instrument.fs_box_v3_v4_v5_faq", + decl_name: "fs_box_v3_v4_v5_faq", + field: WholeDeclaration + } + }, + first_citation: extdeps_external_authority_anchor, + further_citations: [] +} diff --git a/dag/extdeps/instrument/fs_box_v4.dag b/dag/extdeps/instrument/fs_box_v4.dag new file mode 100644 index 00000000000..d55d537e5e2 --- /dev/null +++ b/dag/extdeps/instrument/fs_box_v4.dag @@ -0,0 +1,174 @@ +module extdeps.instrument.fs_box_v4 + +import std.types { Bool, Int, List, NonEmptyStr } +import std.measure { + Micrometer, Millivolt, Celsius, Minute, Gram, Coulomb, + micrometer, millivolt, celsius, minute, gram, coulomb, +} +import std.decl_ref { DeclarationRef, WholeDeclaration } +import extdeps.external_authority { + ExternalAuthority, ExternalModelScope, ExternalSubjectRef, + CitedFigureStanding, CitedToAuthority, +} +import extdeps.uri { Uri, Https } + +// FS BOX V4, THE ORDERABLE MULTI-FUNCTIONAL TRANSCEIVER TOOL KIT, as its 2023 data sheet states +// it. Per DESIGN section 3's external upstream decomposition this module owns this one product. +// The V3/V4/V5 FAQ is a separately published document and lives in +// extdeps.instrument.fs_box_v3_v4_v5_faq; a browser or OS list here is the data sheet's list, not +// a merge. The current fs.com catalog page is not a citation of this module: a fetch of +// www.fs.com/c/fs-box-3389 returned a JavaScript robot-check with no form-factor table, so that +// page is unread rather than an Unobserved property of this product. +// +// NO FS COMPANY ENTITY IS MINTED HERE. Same standing as extdeps.transceiver.fs_145681: no FS +// legal name was read from these documents as a citable legal-entity line. +// +// WHAT THIS MODULE DOES NOT CLAIM. The data sheet does not mention QSFP56, SFP56, breakout legs, +// per-leg programming, disconnection, write recoverability, EEPROM bytes, or NVIDIA (ETH) +// coding. Those absences are the point of reading it. Length, voltage, temperature and endurance +// use std.measure carriers: 28.7 mm is 28700 micrometres, 3.7 V is 3700 millivolts, +// 9 h / 3.5 h are 540 / 210 minutes, 0.2 kg is gram(200), 3200 mAh is coulomb(11520). +// The V3 line on the ordering table is a row of THIS document, not a V3 product module. + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "resource.fs.com/mall/doc/20230224114704wojcth.pdf" + } +} + +type FsBoxV4Size { + height: Micrometer + width: Micrometer + depth: Micrometer +} + +type FsBoxV4Supply { + battery: Millivolt + usb: Millivolt +} + +type FsBoxV4Temperature { + minimum: Celsius + maximum: Celsius +} + +type FsBoxOrderingLine { + part_number: NonEmptyStr + description: NonEmptyStr +} + +type FsBoxV4DataSheet { + product_name: NonEmptyStr + supported_form_factors: List + configurable_media: NonEmptyStr + operating_systems: List + browsers: List + customization_fields: List + data_connector: NonEmptyStr + bluetooth_protocol: NonEmptyStr + charging_connector: NonEmptyStr + power: FsBoxV4Supply + battery_capacity: Coulomb + battery_endurance_low_power_sfp: Minute + battery_endurance_high_power_qsfp_dd: Minute + battery_endurance_condition: NonEmptyStr + case_material: NonEmptyStr + size: FsBoxV4Size + mass: Gram + operating_temperature: FsBoxV4Temperature + ordering: List + warranty: NonEmptyStr + standing: CitedFigureStanding +} + +data fs_box_v4_data_sheet: FsBoxV4DataSheet = FsBoxV4DataSheet { + product_name: "FS BOX V4" as NonEmptyStr, + supported_form_factors: [ + "SFP" as NonEmptyStr, + "SFP+" as NonEmptyStr, + "SFP28" as NonEmptyStr, + "XFP" as NonEmptyStr, + "QSFP+" as NonEmptyStr, + "QSFP28" as NonEmptyStr, + "QSFP-DD" as NonEmptyStr, + ], + configurable_media: "FS transceivers and DAC/AOC cables with rates from 100M to 400G" as NonEmptyStr, + operating_systems: [ + "Windows (10 or newer)" as NonEmptyStr, + "Mac OS (10.13 or newer)" as NonEmptyStr, + "Android (5.0 or newer)" as NonEmptyStr, + "IOS (11.0 or newer)" as NonEmptyStr, + ], + browsers: ["Chrome (68 or newer)" as NonEmptyStr], + customization_fields: [ + "part number" as NonEmptyStr, + "compatible brand" as NonEmptyStr, + "serial number" as NonEmptyStr, + "vendor name" as NonEmptyStr, + ], + data_connector: "Bluetooth or USB Type-C" as NonEmptyStr, + bluetooth_protocol: "Bluetooth Specification V4.2 BLE" as NonEmptyStr, + charging_connector: "USB Type-C" as NonEmptyStr, + power: FsBoxV4Supply { + battery: millivolt(count: 3700), + usb: millivolt(count: 5000), + }, + battery_capacity: coulomb(count: 11520), + battery_endurance_low_power_sfp: minute(count: 540), + battery_endurance_high_power_qsfp_dd: minute(count: 210), + battery_endurance_condition: "without interrupting configuration" as NonEmptyStr, + case_material: "Rubber, Polycarbonate" as NonEmptyStr, + size: FsBoxV4Size { + height: micrometer(count: 28700), + width: micrometer(count: 73500), + depth: micrometer(count: 160000), + }, + mass: gram(count: 200), + operating_temperature: FsBoxV4Temperature { + minimum: celsius(count: -10), + maximum: celsius(count: 50), + }, + ordering: [ + FsBoxOrderingLine { + part_number: "FS BOX V4" as NonEmptyStr, + description: "FS BOX V4, Multi-Functional Transceiver Tool Kit, Support FS Transceivers & DAC/AOC Cables, Red" as NonEmptyStr, + }, + FsBoxOrderingLine { + part_number: "FS BOX V4" as NonEmptyStr, + description: "FS BOX V4, Multi-Functional Transceiver Tool Kit, Support FS Transceivers & DAC/AOC Cables, Yellow" as NonEmptyStr, + }, + FsBoxOrderingLine { + part_number: "FS BOX V3" as NonEmptyStr, + description: "FS BOX V3, Multi-Functional Transceiver Tool Kit, Support FS Transceivers & DAC/AOC Cables" as NonEmptyStr, + }, + ], + warranty: "5-year limited warranty against defects in materials or workmanship" as NonEmptyStr, + standing: CitedToAuthority { authority: extdeps_external_authority_anchor }, +} + +fn fs_box_v4_data_sheet_lists_form_factor(name: NonEmptyStr) -> Bool { + any(fs_box_v4_data_sheet.supported_form_factors, f => (f as String) == (name as String)) +} + +fn fs_box_v4_ordering_names_part(name: NonEmptyStr) -> Bool { + any(fs_box_v4_data_sheet.ordering, line => (line.part_number as String) == (name as String)) +} + +fn fs_box_v4_color_sku_count() -> Int { + fs_box_v4_data_sheet.ordering + |> filter(line => (line.part_number as String) == "FS BOX V4") + |> count +} + +data extdeps_model_scope: ExternalModelScope = ExternalModelScope { + subject: ExternalSubjectRef { + declaration: DeclarationRef { + module_path: "extdeps.instrument.fs_box_v4", + decl_name: "fs_box_v4_data_sheet", + field: WholeDeclaration + } + }, + first_citation: extdeps_external_authority_anchor, + further_citations: [] +} diff --git a/dag/gunbc/extdeps_scope_frontier.dag b/dag/gunbc/extdeps_scope_frontier.dag index b69f1172f27..8449627d90c 100644 --- a/dag/gunbc/extdeps_scope_frontier.dag +++ b/dag/gunbc/extdeps_scope_frontier.dag @@ -326,6 +326,8 @@ data scope_carrier_paths: List = [ "dag/extdeps/github/push_event.dag", "dag/extdeps/instrument/pip_boy_3000.dag", "dag/extdeps/instrument/pip_boy_3000_mk_v.dag", + "dag/extdeps/instrument/fs_box_v4.dag", + "dag/extdeps/instrument/fs_box_v3_v4_v5_faq.dag", "dag/extdeps/linux/edac.dag", "dag/extdeps/linux/kernel.dag", "dag/extdeps/linux/proc_self_cgroup.dag", diff --git a/dag/gunbc/product/breakout_leg_programming.dag b/dag/gunbc/product/breakout_leg_programming.dag index d124d51130c..5d82b51f3ba 100644 --- a/dag/gunbc/product/breakout_leg_programming.dag +++ b/dag/gunbc/product/breakout_leg_programming.dag @@ -13,6 +13,8 @@ import extdeps.transceiver.sff_8636 { sff8636_interpret_extended_compliance, } import extdeps.transceiver.fs_145681 { FsBreakoutCablePart } +import extdeps.instrument.fs_box_v4 { fs_box_v4_data_sheet } +import product.fs_box_published_claims { current_fs_box_internet_constraint_instruction } import product.cable_order_admission { CableLinkRequirement } // WHAT IT COSTS THIS FLEET, BY HAND, TO USE A GENERIC BREAKOUT WHERE A DUAL-COMPATIBILITY ONE WAS NOT @@ -58,13 +60,13 @@ type LegProgrammingProcedure { } data fs_box_v4_leg_programming: LegProgrammingProcedure = LegProgrammingProcedure { - tool: "FS BOX V4" as NonEmptyStr, + tool: fs_box_v4_data_sheet.product_name, legs_programmable_individually: true, requires_disconnect_from_both_ends: true, interrupted_write_recoverable: true, duration_per_leg: "a few minutes" as NonEmptyStr, standing: TranscribedUncited { - read_obligation: "FS's published FS BOX V4 user guide stating per-leg programming of QSFP56 breakout legs, disconnection from both ends, and recovery by re-flash after an interrupted write; until then these four properties rest on FS sales correspondence of 2026-09-11" as NonEmptyStr + read_obligation: "FS BOX V4 Data Sheet (resource.fs.com/mall/doc/20230224114704wojcth.pdf, copyright 2023) and FS BOX V3&V4&V5 FAQ (resource.fs.com/mall/resource/fs-box-v3v4v5-faq-20260824100858.pdf) were read; neither states per-leg programming of QSFP56 breakout legs, disconnection from both ends, recoverability after an interrupted write, or duration per leg. The data sheet's uses of interrupting are battery-endurance sentences only. Until a published user guide states those four properties, they rest on FS sales correspondence of 2026-09-11" as NonEmptyStr }, } @@ -98,7 +100,10 @@ fn breakout_leg_programming_intervention(procedure: LegProgrammingProcedure) -> identity: "breakout-leg-nvidia-eth-reprogram" as HumanInterventionIdentity, frequency: DeviceOnce, subject: "each QSFP56 leg of a generic-coded QSFP-DD breakout serving a ConnectX-7", - instruction: leg_programming_instruction(procedure: procedure), + instruction: concat( + leg_programming_instruction(procedure: procedure), + current_fs_box_internet_constraint_instruction(), + ), completion_evidence: NoDischargeSiteModeled, } } @@ -129,7 +134,7 @@ data fs_nvidia_eth_leg_coding: VendorAssertedLegCoding = VendorAssertedLegCoding coding_profile: "NVIDIA (ETH)" as NonEmptyStr, extended_compliance_code: 64, standing: TranscribedUncited { - read_obligation: "an EEPROM read (ethtool -m raw on) of a leg after an NVIDIA (ETH) re-flash, showing byte 192 at 0x40; until then the value rests on FS sales correspondence of 2026-09-11" as NonEmptyStr + read_obligation: "an EEPROM read (ethtool -m raw on) of a leg after an NVIDIA (ETH) re-flash, showing byte 192 at 0x40; the V4 data sheet and V3/V4/V5 FAQ do not mention byte 192, 0x40, or NVIDIA (ETH) coding, so they do not ground this value; until then it rests on FS sales correspondence of 2026-09-11" as NonEmptyStr }, } diff --git a/dag/gunbc/product/fs_box_published_claims.dag b/dag/gunbc/product/fs_box_published_claims.dag new file mode 100644 index 00000000000..faa1a390484 --- /dev/null +++ b/dag/gunbc/product/fs_box_published_claims.dag @@ -0,0 +1,70 @@ +module product.fs_box_published_claims + +import std.types { Bool, List, NonEmptyStr } +import extdeps.transceiver.sff_8636 { + TransceiverIdentifier, QsfpPlus, Qsfp28, QsfpDd, IdentifierUnmodelled, +} +import extdeps.instrument.fs_box_v4 { + fs_box_v4_data_sheet, fs_box_v4_data_sheet_lists_form_factor, +} +import extdeps.instrument.fs_box_v3_v4_v5_faq { + fs_box_v3_v4_v5_faq, fs_box_faq_lists_box_pro_field, +} + +// JOIN OVER THE TWO CITED FS BOX PUBLICATIONS, NEVER A MERGED SUPPORT LIST. +// +// The 2023 V4 data sheet and the 2026 V3/V4/V5 FAQ disagree on browsers (Chrome-only vs +// Chrome-and-Firefox) and spell the OS list differently. A single "supported" roster would be a +// claim neither document makes. QSFP56 is the same shape: the data sheet omits it; the FAQ never +// names it; this fleet's delivered legs report SFF identifier 0x11 (QSFP28), which the data sheet +// DOES list. Sales correspondence that the QSFP56 legs are programmable stays in +// product.breakout_leg_programming as TranscribedUncited. The fs.com catalog page is not a third +// merged list: a fetch of www.fs.com/c/fs-box-3389 returned a JavaScript robot-check and no +// form-factor table, so the page is unread, not silently listed. +// +// BOX Pro's writable fields are vendor-name / P/N / S/N / compatible-brand as the FAQ printed them. +// They do not include an extended-compliance byte. That is why +// product.breakout_leg_programming's 0x40 claim cannot climb off sales correspondence by reading +// these two documents. + +fn qsfp56_named_on_v4_data_sheet() -> Bool { + fs_box_v4_data_sheet_lists_form_factor(name: "QSFP56" as NonEmptyStr) +} + +fn sff_identifier_named_on_v4_data_sheet(id: TransceiverIdentifier) -> Bool { + match id { + QsfpPlus => fs_box_v4_data_sheet_lists_form_factor(name: "QSFP+" as NonEmptyStr) + Qsfp28 => fs_box_v4_data_sheet_lists_form_factor(name: "QSFP28" as NonEmptyStr) + QsfpDd => fs_box_v4_data_sheet_lists_form_factor(name: "QSFP-DD" as NonEmptyStr) + IdentifierUnmodelled { code: c } => false + } +} + +fn browsers_name_firefox(browsers: List) -> Bool { + any(browsers, b => (b as String) == ("Firefox" as String)) +} + +fn faq_lists_firefox() -> Bool { + browsers_name_firefox(browsers: fs_box_v3_v4_v5_faq.browsers) +} + +fn datasheet_lists_firefox() -> Bool { + browsers_name_firefox(browsers: fs_box_v4_data_sheet.browsers) +} + +fn faq_attests_extended_compliance_rewrite() -> Bool { + fs_box_faq_lists_box_pro_field(name: "extended compliance" as NonEmptyStr) + || fs_box_faq_lists_box_pro_field(name: "byte 192" as NonEmptyStr) +} + +fn fs_box_internet_constraint_instruction(works_offline: Bool) -> NonEmptyStr { + if works_offline { + "The FS BOX may be used offline. " as NonEmptyStr + } else { + "Keep a stable internet connection: the FS BOX V3/V4/V5 FAQ states the tool does not work offline. " as NonEmptyStr + } +} + +fn current_fs_box_internet_constraint_instruction() -> NonEmptyStr { + fs_box_internet_constraint_instruction(works_offline: fs_box_v3_v4_v5_faq.works_offline) +} diff --git a/dag/std/measure.dag b/dag/std/measure.dag index adb072527be..aa4cbe922ef 100644 --- a/dag/std/measure.dag +++ b/dag/std/measure.dag @@ -756,6 +756,32 @@ fn ampere_count(a: Ampere) -> Nat { measure_count(a) } +// MASS at gram grain: SI kilogram is Scale One of Quantity Mass, so a gram is Milli and +// 0.2 kg is gram(200) with no rounding. Kilogram as a named alias waits on a consumer that +// stores a whole-kilogram count; this sheet's 200 g is not one. +type Gram = Measure + +fn gram(count: Nat) -> Gram { + Measure { count: count } +} + +fn gram_count(g: Gram) -> Nat { + measure_count(g) +} + +// ELECTRIC CHARGE: Quantity ElectricCharge's One is the coulomb (ChargeMicro is already +// microcoulombs of the same quantity). 3200 mAh is exactly 11520 C (1 mA·h = 3.6 C). +// Milli of ElectricCharge would be millicoulomb, not milliampere-hour. +type Coulomb = Measure + +fn coulomb(count: Nat) -> Coulomb { + Measure { count: count } +} + +fn coulomb_count(q: Coulomb) -> Nat { + measure_count(q) +} + fn nanometer(count: Nat) -> Nanometer { Measure { count: count } } diff --git a/dag/test/claim/fs_box_published_claims_witness_test.dag b/dag/test/claim/fs_box_published_claims_witness_test.dag new file mode 100644 index 00000000000..3986fb7ec29 --- /dev/null +++ b/dag/test/claim/fs_box_published_claims_witness_test.dag @@ -0,0 +1,144 @@ +module test.claim.fs_box_published_claims_witness + +import std.types { Bool, NonEmptyStr } +import std.measure { + millivolt_count, micrometer_count, minute_count, celsius_count, + gram_count, coulomb_count, +} +import extdeps.external_authority { CitedToAuthority, TranscribedUncited } +import extdeps.transceiver.sff_8636 { transceiver_identifier_from_byte } +import extdeps.instrument.fs_box_v4 { + fs_box_v4_data_sheet, fs_box_v4_ordering_names_part, fs_box_v4_color_sku_count, +} +import extdeps.instrument.fs_box_v3_v4_v5_faq { fs_box_v3_v4_v5_faq } +import product.breakout_leg_programming { + fs_box_v4_leg_programming, fs_nvidia_eth_leg_coding, +} +import product.fs_box_published_claims { + qsfp56_named_on_v4_data_sheet, sff_identifier_named_on_v4_data_sheet, + faq_lists_firefox, datasheet_lists_firefox, faq_attests_extended_compliance_rewrite, + fs_box_internet_constraint_instruction, current_fs_box_internet_constraint_instruction, +} +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +test fn v4_data_sheet_lists_qsfp28_and_qsfp_dd_but_not_qsfp56() -> Bool { + sff_identifier_named_on_v4_data_sheet(id: transceiver_identifier_from_byte(code: 17)) + && sff_identifier_named_on_v4_data_sheet(id: transceiver_identifier_from_byte(code: 24)) + && qsfp56_named_on_v4_data_sheet() == false +} + +test fn observed_identifier_0x11_is_qsfp28_on_the_data_sheet() -> Bool { + sff_identifier_named_on_v4_data_sheet(id: transceiver_identifier_from_byte(code: 17)) +} + +test fn faq_adds_firefox_the_data_sheet_does_not() -> Bool { + faq_lists_firefox() && datasheet_lists_firefox() == false +} + +test fn online_configuration_writes_fewer_fields_than_box_pro() -> Bool { + (fs_box_v3_v4_v5_faq.online_configuration_fields |> count) + < (fs_box_v3_v4_v5_faq.box_pro_fields |> count) +} + +test fn faq_names_hpe_compatible_optics_as_not_reconfigurable() -> Bool { + any( + fs_box_v3_v4_v5_faq.reconfiguration_not_supported, + row => (row as String) == "HPE-compatible SFP+/SFP28/XFP/QSFP+/QSFP28 transceivers", + ) +} + +test fn data_sheet_configurable_media_are_fs_optics_and_cables() -> Bool { + string_contains( + s: fs_box_v4_data_sheet.configurable_media as String, + pattern: "FS transceivers and DAC/AOC cables", + ) +} + +test fn data_sheet_and_faq_spell_macos_differently() -> Bool { + any(fs_box_v4_data_sheet.operating_systems, row => string_contains(s: row as String, pattern: "Mac OS")) + && any(fs_box_v3_v4_v5_faq.operating_systems, row => string_contains(s: row as String, pattern: "macOS")) +} + +test fn data_sheet_two_v4_colors_and_a_v3_ordering_line() -> Bool { + fs_box_v4_color_sku_count() == 2 && fs_box_v4_ordering_names_part(name: "FS BOX V3" as NonEmptyStr) +} + +test fn data_sheet_battery_interrupt_is_endurance_not_write_recovery() -> Bool { + (fs_box_v4_data_sheet.battery_endurance_condition as String) == "without interrupting configuration" + && minute_count(m: fs_box_v4_data_sheet.battery_endurance_low_power_sfp) == 540 + && minute_count(m: fs_box_v4_data_sheet.battery_endurance_high_power_qsfp_dd) == 210 + && coulomb_count(q: fs_box_v4_data_sheet.battery_capacity) == 11520 + && string_contains(s: fs_box_v4_data_sheet.warranty as String, pattern: "5-year limited warranty") +} + +test fn data_sheet_quantities_use_measure_carriers() -> Bool { + millivolt_count(v: fs_box_v4_data_sheet.power.battery) == 3700 + && millivolt_count(v: fs_box_v4_data_sheet.power.usb) == 5000 + && micrometer_count(m: fs_box_v4_data_sheet.size.height) == 28700 + && micrometer_count(m: fs_box_v4_data_sheet.size.width) == 73500 + && micrometer_count(m: fs_box_v4_data_sheet.size.depth) == 160000 + && gram_count(g: fs_box_v4_data_sheet.mass) == 200 + && celsius_count(c: fs_box_v4_data_sheet.operating_temperature.minimum) == -10 + && celsius_count(c: fs_box_v4_data_sheet.operating_temperature.maximum) == 50 +} + +test fn data_sheet_names_bluetooth_4_2_and_usb_c() -> Bool { + string_contains(s: fs_box_v4_data_sheet.bluetooth_protocol as String, pattern: "V4.2 BLE") + && string_contains(s: fs_box_v4_data_sheet.charging_connector as String, pattern: "USB Type-C") +} + +test fn faq_trial_freezes_and_has_a_24_hour_buffer() -> Bool { + (fs_box_v3_v4_v5_faq.trial_period as String) == "seven-day" + && fs_box_v3_v4_v5_faq.trial_frozen_on_expiry + && (fs_box_v3_v4_v5_faq.formal_account_buffer as String) == "24-hour" + && fs_box_v3_v4_v5_faq.custom_compatible_brand_requires_engineering_approval + && string_contains( + s: fs_box_v3_v4_v5_faq.box_pro_customization_review as String, + pattern: "1-2 working days", + ) +} + +test fn faq_does_not_attest_byte_192_rewrite() -> Bool { + faq_attests_extended_compliance_rewrite() == false +} + +test fn faq_internet_requirement_reaches_the_instruction() -> Bool { + current_fs_box_internet_constraint_instruction() + != fs_box_internet_constraint_instruction(works_offline: true) +} + +test fn leg_programming_stays_transcribed_uncited() -> Bool { + match fs_box_v4_leg_programming.standing { + TranscribedUncited { read_obligation: o } => + string_contains(s: o as String, pattern: "neither states per-leg programming") + CitedToAuthority { authority: a } => false + } +} + +test fn nvidia_eth_coding_stays_transcribed_uncited() -> Bool { + match fs_nvidia_eth_leg_coding.standing { + TranscribedUncited { read_obligation: o } => + string_contains(s: o as String, pattern: "do not mention byte 192") + CitedToAuthority { authority: a } => false + } +} + +test fn tool_name_is_the_data_sheet_product_name() -> Bool { + (fs_box_v4_leg_programming.tool as String) == (fs_box_v4_data_sheet.product_name as String) +} + +test fn both_publications_are_cited_to_their_pdfs() -> Bool { + match fs_box_v4_data_sheet.standing { + CitedToAuthority { authority: a } => + (a.uri.locator as String) == "resource.fs.com/mall/doc/20230224114704wojcth.pdf" + TranscribedUncited { read_obligation: o } => false + } + && match fs_box_v3_v4_v5_faq.standing { + CitedToAuthority { authority: a } => + (a.uri.locator as String) + == "resource.fs.com/mall/resource/fs-box-v3v4v5-faq-20260824100858.pdf" + TranscribedUncited { read_obligation: o } => false + } +} + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly diff --git a/src/v1/stage0/src/std_measure.rs b/src/v1/stage0/src/std_measure.rs index 34688efa9aa..cdec17ac1ff 100644 --- a/src/v1/stage0/src/std_measure.rs +++ b/src/v1/stage0/src/std_measure.rs @@ -792,6 +792,32 @@ pub fn ampere_count(a: Ampere) -> Nat { measure_count(a.clone()) } +pub type Gram = Rc>; + +pub fn gram(count: Nat) -> Gram { + Rc::new(Measure { + count: count.clone(), + _phantom: std::marker::PhantomData, + }) +} + +pub fn gram_count(g: Gram) -> Nat { + measure_count(g.clone()) +} + +pub type Coulomb = Rc>; + +pub fn coulomb(count: Nat) -> Coulomb { + Rc::new(Measure { + count: count.clone(), + _phantom: std::marker::PhantomData, + }) +} + +pub fn coulomb_count(q: Coulomb) -> Nat { + measure_count(q.clone()) +} + pub fn nanometer(count: Nat) -> Nanometer { Rc::new(Measure { count: count.clone(),