diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ca58ba..605fb49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,33 @@ ## Unreleased +- Add typed caller diagnostics and coherent JSONDocument byte-span rendering to + JSONHTML, with exact target identity, explicitly missing-parent attachment and + finite cumulative annotation/path/message accounting. Rendering never asserts + schema validity or archives source bytes; replaced roots invent no locations. + +- Add explicit main-actor PDF preparation from immutable static HTML, with genuine + A4/Letter text-range pagination, an explicit continuous mode, cumulative finite + accounting, cancellation/deadline lifecycle and prepared-byte-only transfer. + +- Add opt-in styled RTF to immutable JSONHTML prepared exports. An owned bounded + strict-token emitter preserves pretty JSON text, Unicode scalar sequences and + number spellings; native decoding checks font, colors and actual tab positions. + Finite style/UTF-16 ceilings complement cumulative work/output/retention limits. + Direct JSON defaults remain JSON, HTML, plain text and Markdown. + +- Add immutable prepared JSON, UTF-8 text, Markdown and expanded static HTML in + JSONHTML, plus Apple native JSON Transferable conformance with strict import. + Preparation preserves exact lexemes/scalars/order under one cumulative meter; + selected-format closure guards prevent transport from exposing hidden artifacts. + Real receiving-app/device qualification remains separate. + - Add opt-in JSONHTML interactive disclosure, container outline, literal search, native-byte JSON/Pointer copy and downloads, and complete print content; qualify three browser engines separately from Safari/device/accessibility release gates. - Develop JSONHTML as the fifth product for 1.3: exact formatted standalone HTML, shared strict-writer token formatting, literal tab indentation, cumulative finite reports, bounded script-free output and a fifth native DocC catalogue. - Annotations and transfer/RTF/PDF export remain later increments. - Add local full-language wasm32 WASI support, preserving all four products and the empty root dependency graph. Twelve cumulative UInt64 quotas can exceed Int.max without changing profiles; allocation capacities retain Int guards. diff --git a/Documentation/Architecture/API.md b/Documentation/Architecture/API.md index 73cced2..38c8427 100644 --- a/Documentation/Architecture/API.md +++ b/Documentation/Architecture/API.md @@ -12,7 +12,7 @@ in the [README](../../README.md) are compiled and executed by the public consume | JSONSchema | JSONSchemaDocument, JSONSchemaRegistry, JSONSchemaCompiler, CompiledJSONSchema, JSONSchemaValidator | Offline immutable plan, invocation-owned evaluation and private native regex | | JSONStreaming | JSONEventSequence, JSONValueSequence, NDJSONValueSequence, JSONTopLevelArraySequence, JSONAsyncWriter | Exclusive pull state, shared codec, strict framing and awaited handoffs | | JSONPatch | JSONPatch, JSONPatchOperation, JSONPatchResult, JSONMergePatch | Transaction staging, exact comparison and iterative editing | -| JSONHTML | JSONHTMLRenderer, JSONHTMLRenderingOptions, JSONHTMLLimits, JSONHTMLRenderReport | Shared exact formatting, offline interactive HTML, exact copy/download and cumulative bounded emission | +| JSONHTML | JSONHTMLRenderer, JSONHTMLRenderingOptions, JSONHTMLLimits, JSONHTMLRenderReport, JSONHTMLAnnotation, JSONExportDocument, JSONExportOptions, JSONExportReport | Exact HTML, native Apple transfer, immutable prepared JSON/text/Markdown/static HTML/opt-in styled RTF and cumulative budgets | JSON imports no siblings. JSONSchema depends on JSON and package-private JSONRegexRuntime. Streaming, Patch and HTML depend on JSON. There are five public @@ -191,3 +191,14 @@ UTC time. Prior time-only stored values cannot recover their original calendar date; application migration needs an independent source of that information. Noncanonical Base64 with nonzero unused padding bits now rejects. These corrections restore the documented strict/native contracts; they do not add a permissive mode. + +## Additive 1.3 prepared export + +See the [export contract](JSONHTMLExport.md) for compiler-proven declarations, +finite limits, selected-format transfer guards and the remaining RTF/PDF and +receiving-app qualification boundaries. + +JSONHTMLRenderer also accepts JSONDocument in its four rendering/report methods. +Caller annotations use exact typed paths, explicit missing-parent attachment and +coherent value byte spans without schema execution or source-byte archival. +See [diagnostic rendering](Release1.3/DiagnosticAnnotations.md). diff --git a/Documentation/Architecture/ExtractionManifest.json b/Documentation/Architecture/ExtractionManifest.json index cbf2680..735d569 100644 --- a/Documentation/Architecture/ExtractionManifest.json +++ b/Documentation/Architecture/ExtractionManifest.json @@ -195,7 +195,17 @@ "Native codec, Codable, Pointer, Patch and streaming are original implementations from the integrated contract, not automatically attributed to candidates.", "Schema graph, explicit evaluator, exact sets/assertions and scalar format recognizers are original implementations from Draft 2020-12, RFC3986/3987/3339/3492/5321/5891/5892/5893/6531/6570/6901 and fixed Unicode13 data.", "Private FIPS 180-4 SHA-256 is an original Swift implementation with checked cumulative production accounting, bounded scratch and inner cancellation. The separate trusted fixture tool mirrors its primitive core byte-for-byte; public_check verifies parity. No CryptoKit fallback or runtime dependency is introduced.", - "JSONHTML formatted rendering, original fixed CSS and shared strict-writer token classification are original Apache-2.0 code. No third-party renderer, browser numeric authority or runtime dependency is adapted." + "JSONHTML formatted rendering, original fixed CSS and shared strict-writer token classification are original Apache-2.0 code. No third-party renderer, browser numeric authority or runtime dependency is adapted.", + "JSONHTML typed annotations, iterative exact-path binding, static/interactive attachment and coherent JSONDocument span presentation are original Apache-2.0 code. No schema engine, third-party renderer, source archive or new dependency is adapted." ], - "policy": "Record actual adaptations and retain applicable upstream copyrights and licenses. Reference inputs and comparison experiments are not distributed runtime dependencies." + "policy": "Record actual adaptations and retain applicable upstream copyrights and licenses. Reference inputs and comparison experiments are not distributed runtime dependencies.", + "jsonhtml_export_original_contributions": { + "license": "Apache-2.0", + "adapted_third_party_code": false, + "source": "Sources/JSONHTML/JSONExportDocument.swift; JSONExportOptions.swift; JSONTransferable.swift; JSONRTFWriter.swift; JSONPDFOptions.swift; JSONPDFPreparation.swift; JSONPDFBackend.swift", + "synthetic_fixture_pack_sha256": "5394fccbcd48244004e146189f4c764857cb83c05fd8a2a91380ca4f9d752105", + "framework_boundary": "Conditional CoreTransferable/UniformTypeIdentifiers plus a narrow explicit main-actor Apple PDF boundary using WebKit, CoreText, CoreGraphics and AppKit/UIKit paragraph styles. Core JSON imports none of these; no new root dependency.", + "rtf": "Original bounded strict-token emitter; AppKit/UIKit native decoding only in tests and qualification consumers, not generation.", + "pdf": "Original invocation-owned lifecycle, finite adapter accounting and visible-range pagination using Apple SDK APIs. PDFKit is independent qualification only; no third-party renderer adapted." + } } diff --git a/Documentation/Architecture/JSONHTMLExport.md b/Documentation/Architecture/JSONHTMLExport.md new file mode 100644 index 0000000..9045d71 --- /dev/null +++ b/Documentation/Architecture/JSONHTMLExport.md @@ -0,0 +1,182 @@ +# JSONHTML native transfer and prepared export + +This additive 1.3 development increment provides JSON, UTF-8 text, Markdown and +static HTML, plus explicitly requested styled RTF and separately prepared Apple PDF. +Synchronous preparation rejects PDF; only successful explicit async preparation advertises it. Published 1.2 versions do not +provide this API. + +`JSONExportDocument.prepare(_:options:)` and +`prepareWithReport(_:options:)` synchronously prepare immutable artifacts without +executing WebKit. No source root, mutable cache, native view or temporary file is +retained. `data(for:)` looks up the exact identifier. `selecting(_:)` changes only +per-item advertisement; hidden artifacts remain retained and available for exact +lookup. Empty or unavailable selections fail. There is no public artifact injection. + +Defaults request all four formats, filename stem `JSON`, title `JSON`, one literal +tab per level, LF, light HTML and tab display width four. Indentation is 0...8; +display width is 1...8. `compactJSON` affects native JSON only. Existing writer and +renderer defaults and typed initializer references are unchanged. + +Native JSON uses the strict writer/parser, insertion order and preserved numeric +lexemes. Constructed numbers retain the exact normalized fallback. Whitespace and +original string escape spellings are not source-document reconstruction. Decoded +scalar sequences remain distinct; neither Swift String equality nor floating-point +round trips establish fidelity. Text is the pretty JSON body with quoted string +roots. Markdown adds a `json` code fence longer than any backtick run in the body, +using LF before and after the unchanged body and after the closing fence. + +HTML is expanded and script-free, with fixed hash-authorized CSS and escaped data. +It includes a bounded escaped title and has no disclosure controls, outline, +annotations, line numbers or hidden values. Default light styling is deterministic. +System fonts and the renderer's offline CSP remain in effect. Standalone file +opening and rich-editor paste compatibility are separate qualification claims. + +## Apple transfer boundary + +Importing JSONHTML on supported Apple SDKs introduces the package's sole +`JSON: Transferable` conformance. Its declaration needs no `@retroactive` because +the compiler recognizes the same-package ownership exception. Compilation with +warnings as errors and an independent consumer qualify this placement. + +Direct representations are JSON, static HTML, UTF-8 plain text, Markdown in that +order. Only JSON imports. Every direct request gets fixed finite defaults and its +own invocation. The prepared wrapper is export-only in JSON, HTML, RTF, text, Markdown, PDF order, with +conditions for available/selected artifacts and matching filename extensions. +Closures additionally guard selection: the SDK's generic-text helper can try a +hidden statically declared representation before reaching the selected subtype. +Conditions alone are insufficient to protect that path. An explicit hidden JSON +request fails; Markdown-only generic text reaches Markdown after hidden candidates +are refused. Prefer explicit format selection when a receiver's choice matters. + +CoreTransferable may wrap a parser failure in its transport error. Applications +requiring native structured diagnostics should call JSONParser directly. Transfer +does not add a permissive parser. JSON text import rejects invalid UTF-8, decoded +duplicate keys, trailing syntax and trailing commas under balanced finite limits. + +The initial module links CoreTransferable and UniformTypeIdentifiers conditionally. +Pure preparation is actor-independent and remains available without those modules. +The approved future PDF amendment permits narrow WebKit/platform-print support in +JSONHTML, while core JSON must never import those frameworks. WebKit/CoreText/CoreGraphics and AppKit/UIKit paragraph styles +are confined to explicit PDF generation. PDFKit remains independent qualification only. JH25's old +module-wide UI dependency exclusion is superseded by this explicit boundary. + +## Finite accounting + +One noncopyable JSONWorkMeter admits the root once, then serves cursor formatting, +Markdown scan/wrapping, static HTML, RTF encoding, bounded metadata, artifact tables and result +copies. The HTML helper accepts an already admitted root under that same meter. +Export and HTML core limits intersect before execution; no nested public facade +starts a fresh budget. Checked output growth, cancellation inside owned loops, +copy admission and terminal latching apply throughout. Failure returns no partial +document/report, releases owned scratch and preserves an enclosing reservation. + +Initial export policy: + +| Capacity | Default inclusive ceiling | +| --- | ---: | +| Core | JSONLimits.balanced, all 49 existing metrics | +| One representation | 8,388,608 bytes | +| All prepared artifacts | 16,777,216 bytes | +| Plain title | 4,096 UTF-8 bytes | +| Filename component | 128 UTF-8 bytes | +| RTF syntax color runs | 1,000,000 per artifact | +| Decoded RTF body | 8,388,608 UTF-16 units per artifact | +| RTF tab positions | Int32.max twips, a format parameter bound | +| HTML | JSONHTMLLimits defaults, document ceiling intersected with representation ceiling | + +Zero forbids usage. Allocation capacities must be below Int.max; UInt64.max is +invalid. Checked arithmetic refuses overflow. Title controls are rejected. Filename +components additionally reject empty/dot components, slash, backslash, colon and +bidi controls. No path is derived from the JSON or title. Extensions are fixed. + +Each artifact reserves its logical bytes even if Foundation shares actual storage. +Construction buffers coexist with copies and contribute to peak logical retention; +table/report/selection entries reserve 48 logical bytes per format. The report +contains core statistics, per-format bytes and their aggregate. The title is +released after preparation; the bounded filename remains owned. Hidden artifacts +remain in aggregate counts after selection. Logical retained bytes are not RSS; +owned work units are not caller/transport instructions or an elapsed-time deadline. +Separate public calls require caller composition. There is no failure report that +would justify retrying with a fresh budget. + +These are initial finite policy values, exercised with pinned small fixtures, +generated deep/wide values and inclusive boundary tests. Measurements do not +establish maximum workload throughput, hard RSS or receiving-app resource bounds. +There is no unlimited or device-specific widening preset. + +## Qualification and remaining scope + +`Scripts/ci.py --lane export` runs fixed-count debug/release behavior tests and the +independent five-product consumer, verifies all six fixture hashes, compiles +WebPage declarations on macOS/iPhoneOS/iPhoneSimulator and builds the public mobile +consumers. It records actual commands, exits, source manifest and toolchain. +The consumer records 65 outcomes and eight artifacts, including explicit opt-in RTF +and A4, US Letter and continuous PDFs. It tests native import, exact selected exports, +discovery, suggested filenames, immutable selection and cumulative reports. +SDK builds do not establish simulator/device/share-sheet/drag/drop qualification. + +The T0 offscreen WebPage probe uses nonpersistent per-job state, disables content +JavaScript/subresources, denies external navigation, awaits finished navigation, +sets print media and captures an explicit region. Independent PDFKit inspection +finds the final sentinel on one tall page. This is region capture, not paginated +A4/Letter output or a qualified cancellation/timeout backend. The current paginated backend is qualified separately; neither its public API +nor paper policy is inferred from the historical T0 result. An external +probe watchdog is not a production timeout guarantee. + +The initial receiving/device milestones above are historical. The +[qualification closure](Release1.3/ReviewAndQualification.md) records their actual +source-bound completion, including corrected PDF filenames, delivered drag events +and the approved physical-iPad deferral. SDK builds, automated device outcomes and +manual receiving observations remain separately attributed. Final PR-head checks +are required before a protected merge; tagging and release publication remain separate. + +The synthetic fixture bytes originate in the JSONHTML Transfer/Export briefing +ZIP SHA-256 `5394fccbcd48244004e146189f4c764857cb83c05fd8a2a91380ca4f9d752105`. +Their registry pins all six files; no fixture is executable instruction input. +All export implementation and fixed assets are original Apache-2.0 code; upstream +notices remain intact. No third-party renderer or external package is introduced. + +## Styled RTF increment + +`JSONRTFWriter` is package-only original code. It consumes the existing strict +writer's classified bytes after the single admission. Scalar state is fixed-width; +there is no token-sized buffer, Foundation serialization or native attributed-string +allocation in generation. UTF-8 validation prevents malformed sequences even at +this internal boundary. Unicode is emitted as signed `\uN?` UTF-16 units with +`\uc1`; braces and backslashes are escaped. Actual indentation uses `\tab`, while +JSON string escapes remain visible text. `\par` preserves LF; `\line` failed the +initial independent probe's exact decoded-text requirement. + +The fixed Menlo 11-point font has no external asset. RTF always uses a light palette: +punctuation/whitespace RGB 35/40/48, keys 33/75/110, strings 35/111/59, numbers +143/59/16, booleans 106/58/156, null 100/100/100. Native calibrated-color conversion +is compared in a common color space. HTML theme selection is independent. Each +paragraph gets explicit tab stops for every actual indentation tab, at 132 twips +per display cell times `tabWidth`; native AppKit decoding ignored `deftab` for its +default interval. No arbitrary HTML import, attachment or executable link occurs. +Receiver font/color substitution remains outside byte-export fidelity. + +Style runs count the initial color and every emitted color change; decoded UTF-16 +units include syntax, whitespace and supplementary pairs. Both counters are checked +before emission. Output buffers, wrapping, tab parameters and final result copies +remain charged to the shared meter. New counters do not replace or widen any of +the 49 core metrics. Failure/cancellation latches and releases scratch while +preserving an outer reservation. RTF is export-only and offered only when present +and selected, with a guarded `.rtf` transfer closure and `.rtf` filename. + +The initial finite policies are boundary-tested rather than hard RSS/throughput +claims. Small exact fixtures and generated deep/wide/cancellation inputs support +this increment; maximum-workload calibration and receiving-app qualification +remain distinct release evidence. See [RTF increment](Release1.3/RTFIncrement.md). + +## Explicit PDF increment + +See the [PDF decisions and acceptance map](Release1.3/PDFIncrement.md) and +[DocC guide](../../Sources/JSONHTML/Documentation.docc/PDFPreparation.md). +The new public async MainActor methods use only the document-owned static HTML. +Paginated CoreText visible ranges avoid duplicated text from clipped WebPage regions; +continuous WebPage capture is explicit. One cumulative PDF-phase core meter and +six finite PDF caps cover owned admission, adapter work, captures and copies. +Opaque platform work/allocation and the soft lifecycle deadline are distinct. +Only completed immutable PDF bytes can be selected for transfer. Missing HTML, +failures and cancellation preserve the original item and install no artifact. diff --git a/Documentation/Architecture/Release1.3/Acceptance.json b/Documentation/Architecture/Release1.3/Acceptance.json index 2ad0ad2..b6fcbe6 100644 --- a/Documentation/Architecture/Release1.3/Acceptance.json +++ b/Documentation/Architecture/Release1.3/Acceptance.json @@ -8,8 +8,22 @@ "requirement": "The product/module is JSONHTML in the same package; its target depends on JSON and the exact public set contains five products.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH02", @@ -17,18 +31,46 @@ "requirement": "Forward product policy, manifest/CI assertions, consumers and test-count expectations are revised coherently; historical released records remain accurate.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH03", "gate": "H0", "requirement": "Data/String/report APIs, checked Sendable configuration, finite presets and asset strategy compile through a public consumer.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH04", @@ -36,18 +78,46 @@ "requirement": "All six root kinds, empty containers and mixed deep/wide structures produce a complete strict UTF-8 HTML document.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH05", "gate": "H1", "requirement": "Large integers, exact decimals, negative zero and admitted exponent/number spellings survive display and exported JSON without JS numeric conversion.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH06", @@ -55,28 +125,70 @@ "requirement": "Scalar-distinct composed/decomposed keys/strings and insertion/array order remain distinct and ordered.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH07", "gate": "H1", "requirement": "One noncopyable invocation meter covers admission, traversal, token helpers, paths, assets, Base64, annotations and result copies.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH08", "gate": "H1", "requirement": "Inclusive boundaries and checked overflow cover core work/output/retention and renderer node/DOM/document/outline/annotation capacities.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH09", @@ -84,18 +196,46 @@ "requirement": "Cancellation inside owned loops and valid restrictive/zero ceilings produce honest failures without a successful partial result/report.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH10", "gate": "H1", "requirement": "Repeated and concurrent calls have exclusive state; deterministic output uses stable IDs/assets and no clock/random mutation.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH11", @@ -103,192 +243,463 @@ "requirement": "Data and String materialization charges are complete; final-document byte count is distinguished from cumulative core helper output.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH12", "gate": "H2", "requirement": "Formatted mode has no script; interactive native disclosure remains usable with scripting disabled and allows multiple open branches.", - "status": "partial", + "status": "passed", "evidence": [ "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH13", "gate": "H2", "requirement": "Default expansion, bounded outline, ancestor opening, focus, root/empty/numeric/special-key paths and generated IDs behave coherently.", - "status": "partial", + "status": "passed", "evidence": [ "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH14", "gate": "H2", "requirement": "Copy/download bytes decode to the owned formatted JSON; RFC Pointer metadata preserves controls, CR/LF, NUL and exact scalar sequences.", - "status": "partial", + "status": "passed", "evidence": [ "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH15", "gate": "H2", "requirement": "Raw and escaped Pointer copy modes have explicit labels; unavailable clipboard behavior is reported and manual/UTF-8 alternatives work.", - "status": "partial", + "status": "passed", "evidence": [ "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH16", "gate": "H2", "requirement": "Literal search is bounded/chunked, preserves scalar distinction, opens matched ancestors and reports its rendered-text scope honestly.", - "status": "partial", + "status": "passed", "evidence": [ "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH17", "gate": "H2", "requirement": "Script/style closing strings, ampersands, quotes, event-handler payloads and keys such as __proto__/constructor remain inert data.", - "status": "partial", + "status": "passed", "evidence": [ "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH18", "gate": "H2", "requirement": "No payload-driven innerHTML, executable interpolation, URLs/selectors or script-element text assignment appears in the output/control path.", - "status": "partial", + "status": "passed", "evidence": [ "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH19", "gate": "H2", "requirement": "Emitted CSS/JS asset hashes match CSP block bytes; controls work under policy and external resource requests remain absent.", - "status": "partial", + "status": "passed", "evidence": [ "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open.", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." + ], + "limitation": "Automated browser matrix passed, with explicitly recorded denied favicon events. Native Safari/maintainer VoiceOver scope is recorded separately; annotations, final mobile/platform and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH20", "gate": "H2", "requirement": "Annotations attach by exact typed path or identify missing targets at their parent; no empty list asserts schema validity.", - "status": "not_run", + "status": "passed", "evidence": [ - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." - ] + "DiagnosticAnnotations.md; AnnotationTests.swift (12 native behavior functions); Examples/Consumer public document API; 46-case browser receipt in successor return", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "DiagnosticAnnotations.md; AnnotationTests.swift (12 native behavior functions); Examples/Consumer public document API; 46-case browser receipt in successor return" + ], + "limitation": "Targeted semantic/budget/cancellation and file/served three-engine behavior passed. Final source native/safety/DocC/platform refresh and receiver readiness remain separate gates." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH21", "gate": "H2", "requirement": "JSONDocument locations remain coherent, unmapped/replaced roots invent none, and annotation messages/paths are bounded and escaped.", - "status": "not_run", + "status": "passed", "evidence": [ - "BrowserQualification.md: 37 automated cases and scoped native Safari/maintainer VoiceOver observations; final-platform/annotations clauses remain open." - ] + "DiagnosticAnnotations.md; AnnotationTests.swift (12 native behavior functions); Examples/Consumer public document API; 46-case browser receipt in successor return", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "DiagnosticAnnotations.md; AnnotationTests.swift (12 native behavior functions); Examples/Consumer public document API; 46-case browser receipt in successor return" + ], + "limitation": "Targeted semantic/budget/cancellation and file/served three-engine behavior passed. Final source native/safety/DocC/platform refresh and receiver readiness remain separate gates." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH22", "gate": "H3", "requirement": "Safari, Firefox and Chromium execute file/served, JS on/off, outline/search/copy/download and policy cases with recorded versions.", - "status": "partial", + "status": "passed", "evidence": [ - "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + ], + "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH23", "gate": "H3", "requirement": "Keyboard/focus, text/contrast/themes and relevant VoiceOver behavior are reviewed; accessibility claims match the executed evidence.", - "status": "partial", + "status": "passed", "evidence": [ - "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + ], + "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative recorded receiver/accessibility matrix; physical iPad uses the approved simulator policy. TextEdit rich-save normalization and human-observed speech are explicit; no exhaustive receiver or accessibility certification is claimed." }, { "id": "JH24", "gate": "H3", "requirement": "Print output includes every value regardless of collapse/script state, excludes UI clutter, and preserves applicable UI state.", - "status": "partial", + "status": "passed", "evidence": [ - "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], - "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + ], + "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH25", "gate": "H3", "requirement": "Five-product/platform/resource qualification; pure rendering does not execute UI frameworks, while JSONHTML may link narrowly isolated Apple PDF/printing frameworks.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "FormattedRendererTests.swift; InteractiveRendererTests.swift; AnnotationTests.swift; Examples/Consumer; 46-case browser receipt; ReviewAndQualification.md" ], "amendment": "Maintainer-approved transfer/export framework amendment; original JH25 remains historical.", - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH26", "gate": "H3", "requirement": "Representative generation/output-expansion/retention and browser DOM/interactivity measurements justify finite preset values.", - "status": "partial", + "status": "passed", "evidence": [ "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", - "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "ReviewAndQualification.md; 18 release-native and 54 three-engine browser observations; public consumer export/PDF measurements; retained performance disposition" ], - "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + ], + "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative busy-host calibration supports finite initial policy; not idle A/B, maximum-workload throughput, hard RSS or a browser/platform instruction guarantee. Existing 10% review trigger and dispositions unchanged." }, { "id": "JH27", "gate": "H4", "requirement": "Existing JSONWriter behavior, native/safety/docs lanes, public examples and baseline semantic/corpus cases remain qualified.", - "status": "partial", + "status": "passed", "evidence": [ "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", - "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" ], - "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + ], + "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "JH28", "gate": "H4", "requirement": "Final feature branch/PR head/base, review disposition, source/asset hashes, actual receipts and release placement are recorded.", - "status": "partial", + "status": "passed", "evidence": [ "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", - "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "Interactive increment: InteractiveIncrement.md; 25 HTML Swift Testing functions and pinned 37-case browser harness, actual executed/blocked outcomes in materialized receipts" + ], + "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Implemented native behavior is tested; Firefox startup, native Safari/VoiceOver, final platform integration and final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX01", @@ -296,8 +707,22 @@ "requirement": "Reconcile live source/worktree/renderer branches and preserve unrelated changes before tracked edits.", "status": "passed", "evidence": [ - "Live main/origin, tag, worktree and ruleset reads; isolated codex/1.3-jsonhtml checkout at 532f725; primary checkout unchanged" - ] + "Live main/origin, tag, worktree and ruleset reads; isolated codex/1.3-jsonhtml checkout at 532f725; primary checkout unchanged", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "Live main/origin, tag, worktree and ruleset reads; isolated codex/1.3-jsonhtml checkout at 532f725; primary checkout unchanged" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX02", @@ -305,44 +730,112 @@ "requirement": "Exact five-product manifest, forward policy and CI expectations; no sixth product or external root package dependency.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX03", "gate": "T0", "requirement": "Final public APIs, reports, errors, finite presets and representation builders compile with actual Swift 6.4 / SDK 27.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable.", + "preliminary_observation": "SDK declarations typecheck on macOS/iPhoneOS/iPhoneSimulator 27.2; complete public API/conformance/backend/runtime requirement remains open." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "preliminary_observation": "SDK declarations typecheck on macOS/iPhoneOS/iPhoneSimulator 27.2; complete public API/conformance/backend/runtime requirement remains open.", - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX04", "gate": "T0", "requirement": "Explicit framework amendment; pure generation executes without WebKit; core JSON imports no WebKit/AppKit/UIKit.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX05", "gate": "T0", "requirement": "One JSON conformance in JSONHTML, qualified same-package compiler behavior and independent public consumers.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX06", "gate": "T0", "requirement": "Actual UTType and exported/imported-content-type probes, conditional availability and correct extensions.", - "status": "not_run", - "evidence": [], - "preliminary_observation": "SDK declarations typecheck on macOS/iPhoneOS/iPhoneSimulator 27.2; complete public API/conformance/backend/runtime requirement remains open." + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [], + "preliminary_observation": "SDK declarations typecheck on macOS/iPhoneOS/iPhoneSimulator 27.2; complete public API/conformance/backend/runtime requirement remains open." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX07", @@ -350,8 +843,22 @@ "requirement": "Legacy writer default/output/mutation/initializer behavior retained under spaces, compact and zero indentation.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX08", @@ -359,405 +866,1202 @@ "requirement": "Tabs emitted directly by the cursor; checked 0...8 unit count and separate tab display width.", "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" - ] + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX09", "gate": "T1", "requirement": "Shared helpers admit the input once and keep one noncopyable native preparation meter across selected formats.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX10", "gate": "T2", "requirement": "Exact large numbers, negative zero, exponent spellings and constructed-number fallback; insertion order preserved.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX11", "gate": "T2", "requirement": "Composed/decomposed keys and strings, empty/numeric/control/special keys preserve decoded scalar identity.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX12", "gate": "T2", "requirement": "All six root kinds, empty containers and mixed/deep/wide values are handled without recursive untrusted traversal.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX13", "gate": "T2", "requirement": "Native .json import uses strict parser and finite defaults; invalid UTF-8, duplicate keys and trailing syntax fail.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX14", "gate": "T2", "requirement": "JSON/native JSON equality does not replace separate scalar/order/lexeme checks; no Foundation JSON authority.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX15", "gate": "T2", "requirement": "Plain text contains only the pretty JSON body with quoted scalar-string roots and no UI/fence/labels.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX16", "gate": "T2", "requirement": "Markdown uses json info string, safe fence, actual tabs, LF policy and unaltered JSON body bytes.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX17", "gate": "T2", "requirement": "No mutable global export defaults, shared render cache or heavy ProxyRepresentation getter.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX18", "gate": "T2", "requirement": "Immutable prepared artifacts match advertised/available sets; conditional exports omit missing or unselected data.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX19", "gate": "T2", "requirement": "Selecting unavailable/empty format sets fails; explicit JSON/Markdown/PDF selection advertises exactly that format.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX20", "gate": "T2", "requirement": "Exact data(for:) lookup does no rendering/subtype negotiation; unavailable data produces bounded error.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX21", "gate": "T2", "requirement": "Bounded filename/title validation prevents control/path/separator traversal and selects correct extensions.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX22", "gate": "T2", "requirement": "Wrapper presentation formats are export-only; direct import advertises native JSON only.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX23", "gate": "T2", "requirement": "Direct exports use documented bounded defaults and independently owned state per transport request.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX24", "gate": "T2", "requirement": "One native preparation call is all-or-nothing; failures install no partial artifact or successful report.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX25", "gate": "T3", "requirement": "Static transfer HTML is complete, expanded, highlighted and script-free with deterministic default theme.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX26", "gate": "T3", "requirement": "Static export omits controls/hidden disclosure/line numbers/outline/annotations by default; no JSON truncation.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX27", "gate": "T3", "requirement": "Escaping/CSP/assets hold for script/style delimiters, quotes, ampersands, Unicode/control and prototype-like keys.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX28", "gate": "T3", "requirement": "No external requests/fonts or browser numeric reconstruction; emitted CSS/CSP hashes match actual block bytes.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX29", "gate": "T3", "requirement": "Standalone HTML opening and native rich-paste compatibility are qualified separately.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative recorded receiver/accessibility matrix; physical iPad uses the approved simulator policy. TextEdit rich-save normalization and human-observed speech are explicit; no exhaustive receiver or accessibility certification is claimed." }, { "id": "TX30", "gate": "T3", "requirement": "Per-format and aggregate prepared byte limits include hidden artifacts and overlapping owned buffers.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "not_run", + "evidence": [] + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX31", "gate": "T3", "requirement": "Inclusive minus-one/boundary/plus-one and overflow probes for work/retention/output/metadata/style/HTML caps.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX32", "gate": "T3", "requirement": "Cancellation inside owned loops, result-copy failure and terminal latch behavior preserve accounting.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX33", "gate": "T3", "requirement": "Repeated and concurrent native calls are deterministic and invocation-owned; all state is checked Sendable.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX34", "gate": "T4", "requirement": "RTF is valid styled RTF with token colors/monospace/tab stops; no renamed HTML/Markdown/attachments.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return" + ], + "limitation": "Native emitted RTF text/style qualified. TextEdit rich save normalizes a decomposed key; recipient exact scalar text fidelity is not promised and is explicitly reported." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX35", "gate": "T4", "requirement": "Independent RTF decode matches pretty body with Unicode/surrogate/combining/tab/backslash/brace cases.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return" + ], + "limitation": "Native emitted RTF text/style qualified. TextEdit rich save normalizes a decomposed key; recipient exact scalar text fidelity is not promised and is explicitly reported." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX36", "gate": "T4", "requirement": "RTF strategy has bounded growth/run/UTF-16 range accounting and qualified platform/isolation behavior.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return" + ], + "limitation": "Native emitted RTF text/style qualified. TextEdit rich save normalizes a decomposed key; recipient exact scalar text fidelity is not promised and is explicitly reported." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX37", "gate": "T4", "requirement": "RTF styles remain present in Mac/mobile decoder and real rich-editor observations; recipient limits recorded.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "RTFExportTests.swift; 63-case Mac/iPhone native consumer; actual TextEdit rich reception and saved-file decoding in materialized receiving return" + ], + "limitation": "Native emitted RTF text/style qualified. TextEdit rich save normalizes a decomposed key; recipient exact scalar text fidelity is not promised and is explicitly reported." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative recorded receiver/accessibility matrix; physical iPad uses the approved simulator policy. TextEdit rich-save normalization and human-observed speech are explicit; no exhaustive receiver or accessibility certification is claimed." }, { "id": "TX38", "gate": "T5", "requirement": "PDF preparation is explicit async MainActor work; synchronous native prepare and transfer closures never render PDF.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX39", "gate": "T5", "requirement": "WebPage/WKWebView backend is actually compiled and qualified; WebPage preference/fallback rationale recorded.", - "status": "not_run", - "evidence": [], - "preliminary_observation": "SDK declarations typecheck on macOS/iPhoneOS/iPhoneSimulator 27.2; complete public API/conformance/backend/runtime requirement remains open." + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees.", + "preliminary_observation": "SDK declarations typecheck on macOS/iPhoneOS/iPhoneSimulator 27.2; complete public API/conformance/backend/runtime requirement remains open." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX40", "gate": "T5", "requirement": "Document-owned static HTML prerequisite required; no silent value rerender or unbounded nested quota reset.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX41", "gate": "T5", "requirement": "Nonpersistent isolated WebKit state, JS disabled, denied external navigation/subresources and real layout readiness.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX42", "gate": "T5", "requirement": "Continuous capture complete; paginated A4/Letter output is genuine, measured and never silently downgraded.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX43", "gate": "T5", "requirement": "PDF pages include last sentinel, long/wrapped Unicode content and legible/selectable text without UI clutter.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX44", "gate": "T5", "requirement": "PDF input/output/page/bundle caps and aggregate retention checked; platform work distinguished from native meter.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX45", "gate": "T5", "requirement": "Cancellation/timeout during load/layout/export returns honestly, disposes state and rejects late completions.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX46", "gate": "T5", "requirement": "Concurrent PDF jobs have separate state/cancellation; no singleton web view or unsafe actor annotation.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX47", "gate": "T5", "requirement": "Successful PDF adds one immutable artifact; failure leaves original document and advertisement unchanged.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return", + "ExportTests.swift; RTFExportTests.swift; PDFExportTests.swift; Tools/JSONHTMLExport 65-outcome consumer; ReviewAndQualification.md" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "passed", + "evidence": [ + "PDFIncrement.md; PDFExportTests.swift; Tools/JSONHTMLExport; materialized final-source receipts", + "Mac native/ASAN/TSAN/export/DocC receipts; SDK 27.2 compiler checks; iPhone/iPad simulator 63 outcomes each; final source binding in materialized return", + "Qualified code snapshot 1b4c96186d00ef97f9c6ce4eaeb281a0978493ca: passed native/safety/docs/export lanes and separately bound unchanged-source simulator/PDF inspection receipts in materialized return" + ], + "limitation": "Qualified Mac software, supported Apple SDKs and iPhone/iPad simulator native API/file transport. Receiving-app UI and physical-device PDF integration remain T6 gates; native instructions/RSS/deadline latency are not hard guarantees." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX48", "gate": "T6", "requirement": "Five-product public consumers/resources and Apple framework boundary qualify Mac/iOS/iPadOS SDK/simulator/device cases.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Actual representative physical UI and Mac/iPad receiving checks executed; drag gestures and fresh device filename qualification remain open. See ReceivingQualification.md and materialized receipts." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative recorded receiver/accessibility matrix; physical iPad uses the approved simulator policy. TextEdit rich-save normalization and human-observed speech are explicit; no exhaustive receiver or accessibility certification is claimed." }, { "id": "TX49", "gate": "T6", "requirement": "Actual Mac/iOS/iPad receiving-app matrix records share/drag/paste/open behavior and selected content types.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Actual representative physical UI and Mac/iPad receiving checks executed; drag gestures and fresh device filename qualification remain open. See ReceivingQualification.md and materialized receipts." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative recorded receiver/accessibility matrix; physical iPad uses the approved simulator policy. TextEdit rich-save normalization and human-observed speech are explicit; no exhaustive receiver or accessibility certification is claimed." }, { "id": "TX50", "gate": "T6", "requirement": "Plain-text fallback versus Markdown subtype tested; explicit Copy JSON/Copy Markdown/Share PDF paths qualified.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Actual representative physical UI and Mac/iPad receiving checks executed; drag gestures and fresh device filename qualification remain open. See ReceivingQualification.md and materialized receipts." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative recorded receiver/accessibility matrix; physical iPad uses the approved simulator policy. TextEdit rich-save normalization and human-observed speech are explicit; no exhaustive receiver or accessibility certification is claimed." }, { "id": "TX51", "gate": "T6", "requirement": "Existing renderer JH requirements qualified with explicit JH25 dependency amendment; no hidden weakened baseline.", - "status": "not_run", - "evidence": [] + "status": "passed", + "evidence": [ + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "JH20/JH21 are now implemented and targeted-qualified; full final-source renderer/platform/receiver integration remains open." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX52", "gate": "T6", "requirement": "Native/safety/docs lanes pass on final exact candidate with deliberate nonzero suite/case counts and current base.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX53", "gate": "T6", "requirement": "Generation, combined preparation, RTF/PDF expansion and responsiveness measurements justify frozen finite profiles.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; 18 release-native and 54 three-engine browser observations; public consumer export/PDF measurements; retained performance disposition" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate.", + "limitation": "Representative busy-host calibration supports finite initial policy; not idle A/B, maximum-workload throughput, hard RSS or a browser/platform instruction guarantee. Existing 10% review trigger and dispositions unchanged." }, { "id": "TX54", "gate": "T6", "requirement": "Public docs/examples describe exactness, preparation, format selection, recipient and system-resource boundaries.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX55", "gate": "T6", "requirement": "Separate review pass findings resolved; final API/framework/limits choices and source/log/artifact identity returned.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } + ], + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." }, { "id": "TX56", "gate": "T6", "requirement": "Release slot reconciled; no main push/tag/release/messages under this brief alone; all incomplete rows explicit.", - "status": "partial", + "status": "passed", "evidence": [ - "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests" + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return", + "ReviewAndQualification.md; final required Native/native,safety,docs and Linux receipts; preserved source/evidence manifests" + ], + "history": [ + { + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "record": { + "status": "partial", + "evidence": [ + "First formatted increment: Increment.md; actual local native/safety/docs receipts and authored tests", + "ReceivingQualification.md; final integrated lane and receiver receipts in materialized return" + ], + "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + }, + "policy": "Historical recorded scope retained; later coverage is not attributed to this earlier milestone." + } ], - "limitation": "Only the implemented formatted renderer is covered; interactive, transfer/export, browser, final-platform or final-release clauses remain open as applicable." + "qualification_scope": "Reviewed implementation and source-bound evidence; final PR-head required checks are a separate mandatory merge predicate." } ], "gate_status": { + "H0": "passed", + "H1": "passed", + "H2": "passed", + "H3": "passed", + "H4": "passed", + "T0": "passed", + "T1": "passed", + "T2": "passed", + "T3": "passed", + "T4": "passed", + "T5": "passed", + "T6": "passed" + }, + "historical_gate_status": { "H0": "partial", "H1": "partial", "H2": "not_run", @@ -767,8 +2071,16 @@ "T1": "partial", "T2": "partial", "T3": "partial", - "T4": "not_run", - "T5": "not_run", + "T4": "passed", + "T5": "passed", "T6": "partial" + }, + "qualification_basis": { + "reviewed_implementation_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "diagnostic_envelope_sha256": "f587fbeab66ef5f43eaa9d20057dc80fcc08bfa1c3dd0266f28345fd610b21fd", + "receiver_closure_sha256": "cc9720209057ed6e8d6aa5cf979f97b5853d541f37f8377f942df6d699b9d586", + "closure_guide": "ReviewAndQualification.md", + "history_preserved": true, + "scope": "84 implementation acceptance rows are backed by actual tests/consumers/browser/device/receiver receipts. Any changed final source must pass applicable fresh checks before merge. This ledger does not tag or publish a release." } } diff --git a/Documentation/Architecture/Release1.3/Decisions.md b/Documentation/Architecture/Release1.3/Decisions.md index 855c304..298594b 100644 --- a/Documentation/Architecture/Release1.3/Decisions.md +++ b/Documentation/Architecture/Release1.3/Decisions.md @@ -9,7 +9,7 @@ | Framework amendment | JSONHTML may link Apple PDF/printing frameworks. Pure HTML/native export preparation remains actor-independent and does not execute WebKit. | | Writer compatibility | JSONIndentationUnit defaults to spaces through the retained legacy initializer; a separate overload requires the unit explicitly, preserving typed initializer references. Export tabs/preserved lexemes are explicit. Final declaration requires compiler/behavior evidence. | | Asset strategy | Prefer versioned original Swift constants for fixed CSS/JS; measure and qualify hashes, CSP and full emitted bytes before selection is frozen. | -| RTF | Prefer owned bounded token-based emission. Strategy remains provisional until independent text/style and platform evidence. | +| RTF | Owned bounded strict-token emission selected for the styled increment; independent native decode/style evidence is required. Fixed Menlo 11/light palette, explicit per-paragraph tab stops, UTF-16 surrogate encoding; no platform generator. | | PDF | Prefer modern WebPage where executed behavior qualifies it. Region capture is distinct from pagination; A4/Letter/default geometry and backend remain open. | | Finite presets | No invented values. Freeze additional finite ceilings from native/browser/format measurements; leave core 49 metric values/meanings unchanged. | | Transfer selection | One Apple JSON conformance, JSON-only strict import, immutable per-item export selection. PDF must already be prepared. | @@ -57,3 +57,15 @@ not qualify simulator/device execution, Linux/WASI runtime or transfer/PDF APIs. See [InteractiveIncrement.md](InteractiveIncrement.md) for the compiler-compatible additive options, one-meter metadata/copy path, browser policy, exact tests and remaining Safari/accessibility/Firefox/platform qualification. + +## Native transfer/export increment + +See [TransferExportIncrement.md](TransferExportIncrement.md) and the +[export contract](../JSONHTMLExport.md) for actual four-format declarations, +immutable artifacts, conditional transfer conformance, finite initial capacities +and the explicit future PDF framework amendment. RTF/PDF and real receiving-app +qualification remain separate. Generic type negotiation requires a selected-format +guard in the closure, beyond `.exportingCondition`, on the observed SDK. + +See [RTFIncrement.md](RTFIncrement.md) for the cumulative integration, capacities and +independent native decode checks. PDF and receiving-app qualification remain separate. diff --git a/Documentation/Architecture/Release1.3/DiagnosticAnnotations.md b/Documentation/Architecture/Release1.3/DiagnosticAnnotations.md new file mode 100644 index 0000000..de5179b --- /dev/null +++ b/Documentation/Architecture/Release1.3/DiagnosticAnnotations.md @@ -0,0 +1,40 @@ +# Typed renderer diagnostics and coherent document input + +This increment closes the previously unimplemented JH20/JH21 API slice on the +existing JSONHTML feature branch. It preserves the 43bdb456 receiving candidate and +its materialized evidence as a parent; a successor must be qualified separately. + +Final declarations: JSONHTMLAnnotation (Sendable) owns immutable typed path, severity +and message; its Severity has information/warning/error cases. RenderingOptions adds +an empty annotations property. HTMLLimits adds maxAnnotations = 128 without replacing +existing initializer shapes. Statistics adds accepted annotations. The four existing +value methods retain their function-reference shapes and gain JSONDocument overloads. +No JSONSchema dependency, source-byte archive, implicit validation, parser, line-number +API or automatic Error-description conversion is introduced. + +Binding validates the whole attempted path, resolves its longest existing prefix, +then uses a bounded iterative preorder walk to bind generated ordinals. Scalar-byte +comparisons distinguish keys; negative indices are rejected before resolution. The +qualified initial algorithm can cost nodes times annotations; finite cumulative work +bounds that cost. No asymptotic performance claim or unchecked dictionary/index is +used. Annotation records reserve 64 logical bytes each; traversal/path frames reuse +16-byte core frame policy. Caller payload is borrowed during rendering; result HTML +contains only safely encoded presentation and no root/source ownership. + +Package-scoped source-map scans borrow the same writing meter. Only coherent parsed +mapping yields value spans; missing targets display parent spans explicitly. Mapping +entry/input capacities admit borrowed document metadata before scans. Message/key/index +payload consumes cumulative diagnosticBytes; attempted paths use pointer capacities. +All token/output/escape/DOM/copy work and retention remain cumulative. Failure releases +invocation scratch, preserves enclosing reservations and latches the original error. + +The independent span regression caught optional-nil inference through JSON's nil +literal conformance: a ternary accidentally created a present JSON null for an absent +array element. Explicit optional branches corrected this before qualification; the +failed receipt is retained. The test still expects a missing target and parent span. + +Evidence is recorded in the successor return, including commands, environment, +source manifest, 12 new behavior functions, public consumer, mapped/inert browser +fixtures, exact CI discovery and all 49 baseline probes. Compiler failures and early +failed probes are historical receipts, not passes. Receiver UI/device filename gates +remain separate; no merge/tag/publication is authorized by this increment. diff --git a/Documentation/Architecture/Release1.3/PDFIncrement.md b/Documentation/Architecture/Release1.3/PDFIncrement.md new file mode 100644 index 0000000..967db79 --- /dev/null +++ b/Documentation/Architecture/Release1.3/PDFIncrement.md @@ -0,0 +1,110 @@ +# Explicit PDF prepared-export increment + +This increment continues `codex/1.3-jsonhtml-transfer` from +`703b117c34703292dab26a0420da85d259ba22e2`, stacked on renderer +`3c1e8a1a1bc8d03699665b8ab98f5ece7962aff7` / draft PR #21. Draft PR #22 remains +focused on prepared exports. Parallel branches, main, protection and release +ownership are preserved. Previous immutable RTF candidates remain valid parents. + +## Decisions and observed compiler shapes + +`@MainActor JSONExportDocument.preparingPDF(options:) async throws` returns a new +immutable document; `preparingPDFWithReport(options:)` returns +`JSONPDFPreparationReport`. `JSONPDFOptions`, `JSONPDFLimits`, `JSONPDFPaper`, +`JSONPDFLayout`, `JSONPDFFailure` and `JSONPDFResourceMetric` are portable public +value types. PDF generation is conditional Apple functionality; pure preparation +and direct JSON transfer keep their four defaults. The selected `.pdf` transfer +closure performs only artifact lookup. No root or arbitrary artifact injection +API is introduced. See the [PDF guide](../../../Sources/JSONHTML/Documentation.docc/PDFPreparation.md). + +WebPage remains the preferred nonpersistent/offline HTML loader and continuous +region backend. The initial line-region composition passed unwrapped rows but the +broader wrapped-row test exposed duplicated searchable text at page boundaries. +That strategy is rejected for pagination. An AppKit print spike failed its external +45-second watchdog and left incomplete output; no pagination claim is inferred. +Actual `NSPrintInfo` compilation required the documented dictionary attribute +instead of the proposed nonexistent `jobSavingURL` property. + +The selected paginated backend reads bounded syntax runs from the item's own static +HTML using fixed client-world code, then uses CoreText visible UTF-16 ranges and +CoreGraphics page composition. The independent CoreText spike produced seven pages, +all 150 wrapped markers exactly once and a final-page sentinel. The production +behavior suite then passed A4/Letter, wide strings and zero/144-point margins. +The full native lanes and supported-SDK/runtime receipts must qualify the final +source separately; these initial observations do not replace them. + +The print profile is A4 by default, Letter explicit, 36-point equal margins, +Menlo 11, 16-point line height, character wrapping, white paper and light syntax +colors. Actual prepared tab width is preserved via finite 6.6-point tab intervals. +No title/header/footer/UI is emitted. Continuous output is explicit and bounded to +14,400 total points. Native PDF text extraction can normalize combining sequences +or omit emoji; the exact native JSON artifact remains the interchange authority. + +## Accounting and lifecycle + +One PDF-phase noncopyable meter cumulatively admits input, original hidden bundle, +fixed-script/adapter work, text reservations, intermediate pages, final PDF and +immutable copies. No meter crosses actors. Original export representation/bundle +caps also apply. Core balanced defaults and all 49 baseline metrics remain intact. +Additional PDF caps: HTML/intermediate-page/final bytes each 8 MiB, final bundle +16 MiB, pages 64, content height 14,400 points. The soft deadline is 30 seconds +(default), at most ten minutes. Zero forbids usage and arithmetic is checked. + +Native SDK instructions and allocation are opaque and not covered by owned-work +or hard RSS caps. Native byte buffers are checked after return. Owned logical text +reservations include source and attributed text; they are not allocator measurements. +Cancellation/deadline use a terminal latch, invocation-only stop/detach, immediate +cancellation-state signalling and unstructured workers. No uncooperative loser is +awaited; late completions cannot install output. Native work or main-actor scheduling +can delay observation or outlive return. Concurrent jobs share no page/layout/meter. + +## Acceptance and exact discovery + +TX38–TX47 map to `PDFExportTests.swift`, the independent public consumer and +materialized native/export/safety/docs/SDK/runtime/PDF-inspection receipts. Fifteen +Mac behavior functions are added (five portable Linux functions); root discovery +is 257 on Mac and 244 on Linux. Export discovery is exactly 37 on Mac. The TSAN +filter includes the concurrent PDF cancellation case (nine Mac shared functions; +eight on Linux). The unchanged 173 parameter cases and 49 cap probes remain required. +Native PDF test functions use a serialized Swift Testing suite to avoid overlapping +MainActor browser fixtures; explicit independent concurrent-job behavior stays tested. + +The five-product public consumer preserves its prior 45 outcomes and adds 18 PDF +outcomes (63 total). It emits eight artifacts including A4, Letter and continuous +PDF, independently inspects geometry, all wrapped markers and final sentinels, +and verifies selected transfer/system filename bytes and public cumulative reports. +Native SDK builds and iPhone/iPad simulator outcomes are separate from physical +mobile devices and actual receiving-app share/drag/drop UI. Those gates remain open. + +Final exact-source receipts, source archive/digest, clean reconstruction and review +findings are supplied in the private manifest-backed increment return. Failed +experiments and their outputs/logs are retained there, outside public Git/source. +This document is an implementation/acceptance map, not a substitute for receipts. + +## Review corrections and runtime closure + +The separate review pass corrected artifact-table reservations, released completed +CoreText state before retiring logical text reservations, and moved owned run-array +growth into the job's meter. Client DOM extraction has no owned JavaScript loop; +Swift checks cancellation between individual native reads. Navigation consumes an +admitted finite maximum of eight events and completes at the actual finished event. +Strict memory compilation required two narrowly marked C-interop initializers with +one live CGRect each; a typed `CGMutablePath.addRect` avoids the optional unsafe +transform-pointer initializer. No unsafe actor annotation or weakened flag is used. + +The iOS runtime probe returned a valid 162-point block with nominal 16-point lines. +The old whole-line-grid height guard was disproven by native emoji fallback and +removed; actual finite measured height and real CoreText visible ranges remain +checked. A non-grid geometry regression proves the corrected contract. The final +public consumer passed 63 outcomes on both iPhone and iPad simulators. iPad's first +attempt exhausted disk before its log could be materialized; an independent retry +passed. Neither that failure nor an excluded stale-binary cross-build attempt counts +as success. All failed receipts/probes stay in the private evidence return. + +Final software gates refresh on the closing source commit. Their receipts preserve +257 Mac functions, 173 parameter cases, nine shared-state functions, 49 baseline +caps, 37 export functions, 63 public consumer outcomes and 754 authored declarations. +Caps include actual inclusive native boundaries; uncooperative load/layout/capture +stages separately exercise timeout and late-result disposal. Receiving-app UI and +physical-device PDF integration remain separate open gates. The physical iPad +policy deferral stays in effect; no release action is performed. diff --git a/Documentation/Architecture/Release1.3/Plan.md b/Documentation/Architecture/Release1.3/Plan.md index 6b97fc8..516ed50 100644 --- a/Documentation/Architecture/Release1.3/Plan.md +++ b/Documentation/Architecture/Release1.3/Plan.md @@ -1,6 +1,10 @@ # Swift-JSON 1.3.0 — JSONHTML and exact export -Status: first formatted-rendering increment implemented and locally checked; complete 1.3 release qualification remains pending. +Status: all renderer/export implementation slices and representative receiving +qualification are complete on the reviewed source. See +[ReviewAndQualification.md](ReviewAndQualification.md) for source-bound receipts, +retained scope limits and required final PR-head checks. Tagging and publication +remain separate. The initial sequence and milestone history below are retained. 1.3.0 introduces **JSONHTML**, a fifth library product in the existing Swift-JSON package. It depends on JSON; JSON does not acquire a reverse dependency. The five-product policy becomes effective with the working target, consumers and matching CI checks. Historical four-product releases remain unchanged. JSONRegexRuntime remains private and root external dependencies remain empty. diff --git a/Documentation/Architecture/Release1.3/Progress.json b/Documentation/Architecture/Release1.3/Progress.json index a2aa14c..7c0fed7 100644 --- a/Documentation/Architecture/Release1.3/Progress.json +++ b/Documentation/Architecture/Release1.3/Progress.json @@ -1,12 +1,12 @@ { "schema_version": 1, "target_release": "1.3.0", - "status": "Formatted and interactive increments implemented; browser/platform/final 1.3 gates remain partial", + "status": "Implementation and representative qualification complete; protected PR-stack integration requires passing final-head checks", "actual_base": "532f725740a4d61c32bc9870986d050b71f9db78", "captured_briefing_base": "e9c6693c7cb29df5125e1699fef70f98229ccfdf", - "feature_branch": "codex/1.3-jsonhtml", - "pr": null, - "release_train_dependency": "WASI main 25fb04f integrated; final five-product portable release qualification remains a later gate", + "feature_branch": "codex/1.3-jsonhtml-transfer", + "pr": 22, + "release_train_dependency": "Qualified 1.2/WASI main25fb04f integrated; renderer PR21 merged as d463d0418e0fc201229ae38af6464e56311f6e11; portable five-product Node/Linux scope qualified", "expected_final_products": [ "JSON", "JSONSchema", @@ -16,30 +16,25 @@ ], "current_manifest_products": 5, "qualified_scope": [ - "Briefing integrity only", - "Source/worktree/ruleset orientation", - "Three supported Apple SDK declaration probes only", - "Formatted exact Data/String rendering and cumulative reports", - "16 new behavior functions; complete local native/safety/docs lanes", - "Five-product public consumer and 628 documented declarations", - "JSON/JSONHTML iPhoneOS and iPhoneSimulator module/public-interface typechecks only", - "12 interactive behavior functions; initial native debug/release 217 functions and 173 parameter cases passed", - "Pinned browser harness: 37 required cases, exact byte downloads and explicit unexecuted engine counts", - "All 37 browser cases executed; native Safari exact downloads/focus and four maintainer-observed VoiceOver checks" - ], - "implementation_qualified": false, - "next_increment": "Complete blocked browser/accessibility/final-platform qualification; continue separately scoped annotation and transfer/export work", - "open_decisions": [ - "Measured finite renderer/export/PDF presets", - "Executed PDF backend/pagination/defaults", - "Qualified RTF strategy", - "Exact final declaration shapes" + "Native 270 functions/173 parameters; ASAN270/173; TSAN10; strict memory warnings-as-errors", + "Portable Linux/Node 256 functions/173 parameters; WasmKit14 public consumer functions/probes, not full root", + "All49 baseline cap probes and populated pinned independent suites with exclusions separate", + "38 focused export functions;65 public consumer outcomes/eight artifacts; three Apple SDKs", + "65 actual consumer outcomes on Mac, physical iPhone and iPhone/iPad simulators", + "46 browser cases/zero skips; native Safari exact/diagnostic navigation and human VoiceOver scope", + "Three delivered Mac exact JSON/Markdown/generic String drop callbacks; physical and simulated A4.pdf Files save/reopen", + "Five compiled DocC catalogues,771 declarations and13 executed README fences", + "18 release-native representative measurements;54 browser observations across six frozen workloads/three engines", + "Immutable source/evidence archives, clean reproduction and reviewed public framework/dependency boundary" ], + "implementation_qualified": true, + "next_increment": "Complete protected PR22 integration after current required checks and source review; post-merge verification, tagging and publication remain separate", + "open_decisions": [], "notes": [ - "No raw private briefing packs or source-clone records enter Git", - "No empty product or passing placeholder tests", - "Existing core defaults and 49 metric meanings retained", - "No messages, merge, release or protection changes" + "Private briefs, device records, logs and build outputs remain outside public Git/source archives", + "Original milestone objects and failed-probe history retained", + "All49 metric values/meanings and10% performance review trigger unchanged", + "Explicit maintainer authorization2026-10-14 permits protected PR21/22 review and merge only; no tag/release publication, direct main push or protection bypass" ], "input_packets": [ { @@ -55,7 +50,7 @@ "entries": 8 } ], - "updated_utc": "2026-10-13T21:53:26.614788+00:00", + "updated_utc": "2026-10-14T20:31:44.666625+00:00", "evidence_locations": [ "Scripts/ci.py --lane native --output .build/ci/jsonhtml-native-compatible", "Scripts/ci.py --lane safety --output .build/ci/jsonhtml-safety-compatible", @@ -67,7 +62,7 @@ "Scripts/ci.py --lane docs --output .build/ci/jsonhtml-interactive-final-docs", "Scripts/ci.py --lane browser --output .build/ci/jsonhtml-interactive-final-browser" ], - "final_source_refresh": "The final-source lane receipts are materialized privately after committing this source; initial full lanes already passed. Any final failure keeps that lane open.", + "final_source_refresh": "Current required Native/native,safety,docs and supporting Linux checks must pass on the final PR head before merge; preserve byte-identity reuse for unchanged implementation, assets, fixtures and tests.", "interactive_increment": { "parent_commit": "41fdf1c5045ef171059981ffcbc59a5aef8f998a", "expected_root_functions": 217, @@ -104,5 +99,212 @@ 25 ], "discovery_correction": "Inherited WASI three added JSON tests require 107, not 104; actual successful test exit initially failed stale harness cardinality. Failure preserved; 173 parameter cases and 49 probes unchanged." + }, + "transfer_export_increment": { + "branch": "codex/1.3-jsonhtml-transfer", + "base": "3c1e8a1a1bc8d03699665b8ab98f5ece7962aff7", + "upstream_main": "25fb04f747e583209b4c4511abf39e11ef5b4f80", + "status": "four-format implementation; full T0-T6 qualification incomplete", + "new_swift_testing_functions": 16, + "expected_apple_root_functions": 236, + "expected_parameter_cases": 173, + "preserved_metrics": 49, + "conformance": "single conditional JSON: Transferable in JSONHTML; compiler-owned package exception, no retroactive annotation", + "shared_preparation": "one root admission, one cumulative noncopyable meter, selected-format closure guards", + "pdf_probe": "offscreen WebPage region: one 595 x 3500 point page, final sentinel; not A4/Letter pagination", + "incomplete": [ + "styled RTF", + "qualified paginated PDF and lifecycle cancellation", + "Mac/mobile receiving-app UI and simulator/device runtime", + "final five-product portable qualification" + ], + "evidence": [ + "../JSONHTMLExport.md", + "TransferExportIncrement.md", + "Scripts/ci.py --lane export", + "Tools/JSONHTMLExport" + ] + }, + "styled_rtf_increment": { + "branch": "codex/1.3-jsonhtml-transfer", + "parent": "6f1c6ebe442bd51119ca0486b78bf9d90da561ba", + "status": "Implemented; exact-source receipts required before qualification claim", + "new_swift_testing_functions": 6, + "expected_export_functions": 22, + "expected_apple_root_functions": 242, + "expected_linux_root_functions": 239, + "expected_parameter_cases": 173, + "preserved_metrics": 49, + "public_consumer_outcomes": 45, + "artifacts": 5, + "remaining": [ + "PDF backend/pagination/lifecycle", + "RTF rich-editor and physical-device integration", + "Final five-product portable release qualification" + ], + "evidence": [ + "RTFIncrement.md", + "Scripts/ci.py --lane export", + "Scripts/qualify_rtf_runtime.py", + "native/safety/docs receipts in materialized return" + ] + }, + "pdf_increment": { + "branch": "codex/1.3-jsonhtml-transfer", + "parent": "703b117c34703292dab26a0420da85d259ba22e2", + "status": "T5 software/SDK/simulator increment qualified; final 1.3 release/recipient/device gates remain separate", + "public_shape": "Explicit async MainActor preparation returning an immutable document or successful operation report", + "backend": "WebPage static syntax + CoreText visible-range pagination + CoreGraphics; explicit WebPage continuous region", + "new_apple_functions": 15, + "new_linux_functions": 5, + "expected_apple_root_functions": 257, + "expected_linux_root_functions": 244, + "expected_export_functions": 37, + "expected_mac_shared_functions": 9, + "preserved_parameter_cases": 173, + "preserved_metrics": 49, + "public_consumer_outcomes": 63, + "artifacts": 8, + "evidence": [ + "PDFIncrement.md", + "Scripts/ci.py --lane export", + "Scripts/qualify_rtf_runtime.py", + "Materialized final-source native/safety/docs receipts and independent PDF inspection" + ], + "remaining": [ + "RTF/PDF receiving-app UI and physical-device integration", + "Final five-product portable release qualification" + ], + "initial_runtime_code": "3fa8c734c20f3eb6440e7b4d204a80ff570a4d89", + "simulator_outcomes": { + "iPhone": 63, + "iPad": 63 + }, + "review_dispositions": [ + "Cumulative artifact-table reservations and layout-state release corrected", + "Owned syntax-run admission/cancellation and bounded navigation corrected", + "Two audited C pointer calls; typed safe rectangle path", + "Disproven iOS line-grid assumption removed; measured geometry retained" + ], + "qualified_code_commit": "1b4c96186d00ef97f9c6ce4eaeb281a0978493ca", + "source_archive_sha256": "b0de3414b0beefe94802358073161c31e74d9735a5b3cf8792de70c26e4c9236", + "passed_lanes": [ + "native: 257 functions / 173 parameters / 49 probes", + "safety: ASAN 257 / 173; TSAN 9; strict memory warnings-as-errors", + "docs: 754 authored declarations / 13 executable fences / five catalogues", + "export: 37 debug and release / 63 consumer outcomes / 8 artifacts / three SDKs" + ], + "closure_policy": "This documentation-only successor reuses verified unchanged implementation/test/consumer evidence; actual final archive, source binding and clean reconstruction remain in the materialized return." + }, + "receiving_export_qualification": { + "implementation_head": "1e0b86d2580bb9678039597523aa3b99d2905abf", + "branch": "codex/1.3-jsonhtml-transfer", + "pr": 22, + "status": "Seven hosted checks and native/safety/docs/export/browser/WASI source88 passed; provider filename regression discovered and corrected; affected successor qualification required", + "native_consumer": { + "mac": 63, + "physical_iPhone": 63, + "artifacts": 8 + }, + "physical_ui": "Maintainer reported all four representative PDF/local Files/JSON/Markdown checks passed; automated button receipts are separate.", + "mac_receivers": "ShareLink PDF/Copy exact; plain TextEdit saved bytes exact; RTF styles retained but rich save canonicalizes decomposed key; HTML rich paste observed.", + "wasi": "Inner complete five-product runtime passed; both stale inner/outer product/count tables repaired, final lane refresh required.", + "unimplemented_renderer_gates": [ + "JH20 typed diagnostic annotations", + "JH21 coherent JSONDocument source locations" + ], + "physical_iPad": "Existing maintainer deferral retained; simulator scope explicit.", + "evidence": "ReceivingQualification.md; actual source-bound materialized private receipts", + "release_actions": "No merge/tag/publication/protection or parallel checkout edits", + "provider_filename_regression": { + "initial_source": "88abf5c4be75035b0b6571ffa623a891b32aa864", + "before": "NSItemProvider selected non-JSON formats suggested .json; new regression failed1 function/5 issues", + "correction": "Hidden filename metadata follows first advertised preference; typed requests keep their own format name", + "native_functions": 258, + "export_functions": 38, + "consumer_outcomes": 65, + "portable_functions": 244, + "remaining": "Fresh recipient/device filename and successor native qualification; JH20/JH21 still open" + } + }, + "diagnostic_annotation_increment": { + "status": "implemented_targeted_qualified", + "parent": "43bdb456b29d416253ffbfdaf158ccc25ce4dee4", + "acceptance": [ + "JH20", + "JH21" + ], + "new_behavior_functions": 12, + "root_functions": { + "mac": 270, + "linux_wasi": 256 + }, + "html_functions": { + "mac": 75, + "linux_wasi": 61 + }, + "browser_cases": 46, + "public_api": "JSONHTMLAnnotation; options.annotations; limits.maxAnnotations; statistics.annotations; four JSONDocument renderer overloads", + "targeted_results": "12 native functions and 46 browser cases passed; final source lane refresh required after integration metadata edits", + "remaining": "Receiver drag/drop and fresh physical filename/UI checks; full final candidate review and qualification. Original receiving/renderer history retained." + }, + "historical_progress_header": { + "status": "Formatted and interactive increments implemented; browser/platform/final 1.3 gates remain partial", + "feature_branch": "codex/1.3-jsonhtml", + "pr": null, + "release_train_dependency": "WASI main 25fb04f integrated; final five-product portable release qualification remains a later gate", + "qualified_scope": [ + "Briefing integrity only", + "Source/worktree/ruleset orientation", + "Three supported Apple SDK declaration probes only", + "Formatted exact Data/String rendering and cumulative reports", + "16 new behavior functions; complete local native/safety/docs lanes", + "Five-product public consumer and 628 documented declarations", + "JSON/JSONHTML iPhoneOS and iPhoneSimulator module/public-interface typechecks only", + "12 interactive behavior functions; initial native debug/release 217 functions and 173 parameter cases passed", + "Pinned browser harness: 37 required cases, exact byte downloads and explicit unexecuted engine counts", + "All 37 browser cases executed; native Safari exact downloads/focus and four maintainer-observed VoiceOver checks" + ], + "implementation_qualified": false, + "next_increment": "Complete blocked browser/accessibility/final-platform qualification; continue separately scoped annotation and transfer/export work", + "open_decisions": [ + "Measured finite renderer/export/PDF presets", + "Executed PDF backend/pagination/defaults", + "Qualified RTF strategy", + "Exact final declaration shapes" + ], + "notes": [ + "No raw private briefing packs or source-clone records enter Git", + "No empty product or passing placeholder tests", + "Existing core defaults and 49 metric meanings retained", + "No messages, merge, release or protection changes" + ], + "updated_utc": "2026-10-14T13:48:05.774840+00:00", + "final_source_refresh": "The final-source lane receipts are materialized privately after committing this source; initial full lanes already passed. Any final failure keeps that lane open." + }, + "current_qualification": { + "guide": "ReviewAndQualification.md", + "reviewed_implementation_head": "2872afee29ba65905a16653eee8ea4f3807e14e5", + "receiver_closure_sha256": "cc9720209057ed6e8d6aa5cf979f97b5853d541f37f8377f942df6d699b9d586", + "browser_measurements": { + "observations": 54, + "engines": 3, + "workloads": 6, + "source_head": "2872afee29ba65905a16653eee8ea4f3807e14e5" + }, + "required_checks": [ + "Native / native", + "Native / safety", + "Native / docs" + ], + "github_approvals_required": 0, + "unresolved_review_threads": 0, + "no_independent_reviewer_claim": true, + "retained_limits": [ + "Physical iPad simulator policy", + "TextEdit rich-save Unicode normalization", + "Representative calibration only; no maximum-workload/RSS/opaque SDK instruction guarantees", + "Formatted audible speech not part of the new interactive annotation check" + ] } } diff --git a/Documentation/Architecture/Release1.3/RTFIncrement.md b/Documentation/Architecture/Release1.3/RTFIncrement.md new file mode 100644 index 0000000..7be2d46 --- /dev/null +++ b/Documentation/Architecture/Release1.3/RTFIncrement.md @@ -0,0 +1,35 @@ +# Styled RTF prepared-export increment + +This work continues `codex/1.3-jsonhtml-transfer` from `6f1c6ebe442bd51119ca0486b78bf9d90da561ba`, +stacked on renderer `3c1e8a1a1bc8d03699665b8ab98f5ece7962aff7` / draft PR #21. +Draft PR #22 retains its base and existing history. Parallel work and release +ownership remain unchanged. No merge, tag, publication or protection change occurs. + +The [export contract](../JSONHTMLExport.md) and [DocC guide](../../../Sources/JSONHTML/Documentation.docc/PreparedExport.md) +describe explicit `.rtf` preparation. Public methods are unchanged; finite +`maxRTFStyleRuns` and `maxRTFUTF16Units` are appended defaulted initializer arguments. +Direct JSON transfer and prepared defaults remain the four baseline formats. +Prepared RTF is export-only and guarded by immutable selection. + +Five portable Swift Testing functions exercise escaping, inclusive caps, composed +budgets, terminal failure and cancellation during encoding. One Apple native +function independently verifies exact decoded bytes/scalars/lexemes/order, all six +root kinds, font/colors, UTF-16 ranges and actual tab positions. The existing +16-task shared export test now also includes RTF and remains in the TSAN filter. +Exact export discovery is 22 functions on Mac, root discovery 242 with the unchanged +173 parameter cases; Linux root discovery is 239. The original 49 metric probes, +independent suites and policy exclusions remain required. + +The independent public consumer preserves the original 35 outcomes and adds ten +RTF outcomes (45 total), emits five artifacts, and checks one admission, cumulative +work/retention, native text/styles, selected transfer and system filename bytes. +Three supported SDKs compile all five products. Optional isolated iPhone/iPad +simulator runs qualify UIKit decoding separately; physical devices and rich-editor +UI remain explicitly unqualified for this increment. + +Required source-bound receipts: native, safety, docs and export CI lanes, optional +RTF simulator receipt, clean archive reconstruction and separate source review. +Materialized logs and artifacts stay outside Git. Any failed/pending receipt keeps +its gate open; this plan is not a substitute for actual outcomes. Previous +immutable source and delivery archives remain preserved. PDF remains a distinct +backend/pagination/lifecycle milestone, with no public preparation API added here. diff --git a/Documentation/Architecture/Release1.3/ReceivingQualification.md b/Documentation/Architecture/Release1.3/ReceivingQualification.md new file mode 100644 index 0000000..6d5379d --- /dev/null +++ b/Documentation/Architecture/Release1.3/ReceivingQualification.md @@ -0,0 +1,110 @@ +# Receiving-app and physical export qualification + +The observations below retain the earlier receiving candidates and failures. The +[final qualification closure](ReviewAndQualification.md) records the subsequent +65-case source, successful delivered drag events, corrected PDF save/reopen and +annotation Safari/VoiceOver checks. Historical open states below do not describe +the current reviewed candidate. + +This pass continues export implementation 1e0b86d2580bb9678039597523aa3b99d2905abf +on codex/1.3-jsonhtml-transfer, stacked on renderer PR #21. It changes qualification +tools and documentation, not the JSONHTML public API or library implementation. +Actual source hashes, app binaries, commands, failed attempts and observations are +materialized in the local handoff; this document is a scope map, not a receipt. + +## Observed receiver behavior + +The five-product native consumer passed 63 outcomes and produced eight artifacts +on macOS 27.0 and a development-signed physical iPhone 14 Pro Max running iOS 27.2. +The device uses the existing Swift 6.4 / SDK 27.2 baseline and packaged schema +resources. Its representative UI checks were maintainer-observed: seven-page PDF +and final sentinel; Share PDF to local Files and reopen; explicit Copy JSON and +Share JSON/Copy into the strict reader; explicit Copy Markdown into its reader. +The maintainer reported all four checks passed. These are manual UI observations, +separate from the automated consumer and from unexecuted recipients. + +On macOS, the actual ShareLink PDF/Copy action produced com.adobe.pdf bytes equal +to the prepared A4 artifact. Explicit Copy JSON and Copy Markdown reached their +typed readers unchanged. TextEdit 1.21 plain paste/save preserved all 105 UTF-8 bytes, +including scalar-distinct keys, exact integer and number spelling. + +TextEdit rich RTF paste and its saved file retain Menlo-Regular 11 and distinct +token colors. The saved RTF canonicalizes the decomposed key to composed U+00E9: +exact scalar text fidelity at this recipient save layer fails. The emitted RTF +independently decodes exactly in the native consumer. No emitter normalization, +format relabeling or hidden fallback is introduced. Use the native JSON artifact +for interchange. HTML paste is separately observed: TextEdit receives the complete +visible data, document heading and highlighted tokens; its own paragraph/font +choices remain recipient behavior. + +The private SwiftUI qualification app uses the actual compiled ShareLink shape: +a non-String/non-URL item needs an explicit SharePreview. Its wrapper adapts only +the existing consumer entry/output URL, resource-bundle access and an observation +type name that otherwise shadows Apple's Observation module. It introduces no +second JSON conformance or production renderer. + +Background startup of that private GUI app hit the unchanged 30-second soft PDF +deadline. Explicit foreground preparation passed. The failed observation is retained; +this does not establish a hard background responsiveness guarantee or justify +widening a default. Native platform work and main-actor scheduling remain separate +from owned quotas and logical retention. + +## Integrated portable harness + +The inherited WASI harness still asserted four products and 104 JSON functions. +Actual final-source discovery includes 107 JSON functions, including the three +quota/cumulative-overflow regressions, plus 49 portable JSONHTML functions. +Both inner and outer exact expectations now require those bundles. The 173 +parameter cases and all 49 baseline cap probes remain unchanged. + +The existing 13-function WASI public consumer gains one meaningful Swift Testing +function covering six portable checks: exact static HTML, complete interactive +CSP bytes, native/text/Markdown/HTML/styled RTF preparation, finite HTML exhaustion, +deterministically canceled preparation and the explicit unavailable PDF boundary. +Both Node and WasmKit execute these checks; Apple transfer/PDF functionality is +never inferred from portable rendering. The complete debug/release root suites, +independent corpus policy accounting and frozen rooted consumer remain required. + +## Open integrated gates + +The final receiving/platform ledger records which Mac and simulator drag/share/ +reader interactions actually executed. Undelivered automation gestures do not +count as passes; manual observations are identified as such. Physical iPad +qualification retains the maintainer's existing deferral. + +JH20/JH21 diagnostic annotations and coherent JSONDocument source-location input +remain unimplemented in the renderer. Their associated annotation accounting and +full renderer/release gates remain open. This qualification pass does not turn +those absent APIs into evaluated successes or make the complete 1.3 release ready. +Affected native/safety/docs/browser and portable checks must be bound to the +final feature source. No merge, tag, publication, protection change or parallel +checkout modification is performed. + +## Receiver metadata regression + +The actual iPadOS 27 Files share sheet exposed A4.json for a PDF-only selection. +A public NSItemProvider probe reproduced the same JSON suffix for HTML, RTF, +plain text and Markdown although their registered content types were correct. +The original selected-type/file-export checks did not inspect the provider's +single suggestedName. A new Swift Testing regression failed with five issues +before the correction and passed afterward. The filename callback now retains +its own advertised format, or the first actual advertised preference when the +SDK asks for hidden metadata. No shared cache, data conversion or fallback is +introduced. The consumer adds two provider outcomes, including prepared PDF: +65 outcomes / eight artifacts; native root 258 / 173, export 38; portable root +244 / 173 and HTML 49 remain unchanged. Fresh receiver/native qualification is +required for this source change; the earlier 63-case device record is historical. + +The iPad simulator's first migration wait timed out, then its actual completed +migration allowed the unchanged 63-case consumer to pass. CUA observed JSON and +Markdown copy/paste readers passing. Desktop and iPad drag attempts delivered no +validated drop event; those gestures remain open, not passing library tests. +The Mac UI tool also experienced a long locked-host wait. Such automation/SDK +observations are not an owned-instruction or responsiveness guarantee. + +## Diagnostic successor + +The parent receiving snapshot above remains historical. The subsequent +[diagnostic increment](DiagnosticAnnotations.md) implements JH20/JH21 with 12 +behavior functions and an expanded 46-case browser harness. Receiver filename, +actual drag/drop and final source qualification are separate remaining gates. diff --git a/Documentation/Architecture/Release1.3/ReviewAndQualification.md b/Documentation/Architecture/Release1.3/ReviewAndQualification.md new file mode 100644 index 0000000..7b7dbbc --- /dev/null +++ b/Documentation/Architecture/Release1.3/ReviewAndQualification.md @@ -0,0 +1,121 @@ +# 1.3 implementation review and qualification + +The renderer, native preparation, styled RTF, explicit Apple PDF and typed diagnostic +slices are implemented and qualified in the representative scope below. The +reviewed implementation is `2872afee29ba65905a16653eee8ea4f3807e14e5`. Renderer +[PR #21](https://github.com/ThorFuchs/Swift-JSON/pull/21) merged as +`d463d0418e0fc201229ae38af6464e56311f6e11`; the child-only commits in +[PR #22](https://github.com/ThorFuchs/Swift-JSON/pull/22) were transplanted onto +that identical renderer tree without changing any source bytes. Documentation +corrections are a separately checked successor. Required checks and review-thread +resolution must pass on its actual final head before merge. The maintainer's +2026-10-14 authorization permits the protected stack merge; tagging and release +publication remain separate. + +## Review findings + +A separate source-and-contract review covered the shared writer cursor and original +initializer function type; exact typed/scalar identity; HTML/RTF escaping, fixed CSP +and offline navigation; cumulative admission/output/retention; cancellation and +late PDF results; immutable selection/transfer filenames; conditional framework +placement; consumers, independent fixtures and exact test discovery. No unresolved +code defect was found. This is an agent source review, not a claim of an independent +second reviewer or a GitHub approving review. Effective main rules require zero +approvals, resolved threads and strict Native / native, Native / safety and +Native / docs checks; bypass actors are empty. No protection was changed. + +Three documentation findings were corrected: README incorrectly called annotations +deferred; current CI/export inventory retained old counts; and the acceptance/progress +headers retained earlier not_run/partial states after the corresponding source-bound +passes. Historical records and their original limitations are preserved. The 84-row +acceptance ledger records implementation qualification; it does not substitute for +live required checks, certify every receiving app or publish a release. + +## Source-bound evidence + +The materialized source archive SHA-256 is +`36b21a3805d0b187d77bbe4bd173443e963ced4fd00c700f7c681ee2790cd44a`: +886 source files plus its manifest. Clean extraction/build/test, two byte-identical +normalized archives and exact final-source bindings were verified. The diagnostic +qualification envelope SHA-256 is +`f587fbeab66ef5f43eaa9d20057dc80fcc08bfa1c3dd0266f28345fd610b21fd`. +The receiving/accessibility closure envelope is 57,927,484 bytes, SHA-256 +`cc9720209057ed6e8d6aa5cf979f97b5853d541f37f8377f942df6d699b9d586`. +It includes the actual source and parent envelopes, new receipts/logs and artifacts. +A separate recursive audit verified all three envelope manifests, 844 scoped log +references, 1,552 historical aliases and three unique source-archive manifests. +Private device records and build products remain outside public Git/source archives. + +| Area | Executed evidence | +| --- | --- | +| Mac behavior | Native debug/release 270 functions and 173 parameter invocations; all 49 cap probes and the explicitly rooted 20-outcome public consumer | +| Safety | ASAN 270/173; ten shared/concurrent functions under TSAN; strict-memory-safety build with warnings as errors | +| Independent inputs | Syntax 318 observed = 287 evaluated successes + 31 policy exclusions; schema 2,337 = 2,300 successes + 37 exclusions; authored semantics 82, budget boundaries 9, malformed replay 4,396 | +| Export | 38 focused debug/release functions; 65 public consumer outcomes and eight artifacts; macOS, iPhoneOS and iPhoneSimulator SDK 27.2 compiler/consumer builds | +| Actual runtimes | The same 65 outcomes/eight artifacts executed on Mac, physical iPhone 14 Pro Max iOS 27.2, iPhone simulator and iPad simulator iOS/iPadOS 27.0; these are not 260 distinct behavior cases | +| Browsers | 46 cases across pinned Chromium/Firefox/WebKit, zero skips; native Safari 27.0 navigation/file/print checks remain separately recorded | +| Portable | Linux ARM64/x86-64 native/safety passed; Node debug/release 256 functions/173 parameters; 71 WASI commands. WasmKit remains limited to its recorded 14-function public consumer/probes/command parity scope | +| Documentation | Five compiled DocC catalogues, 771/771 authored declarations and 13 executable README fences; affected documentation is refreshed for the correction | + +Commands, exits, exact input/source/asset hashes and environment are in the receipts, +not inferred from summaries. Native tools were Apple Swift 6.4, language mode 6, +Xcode/SDK 27.2 on macOS 27.0 ARM64. Required Native and supporting Linux checks +passed on the reviewed source; final-head checks must be read again after integration. +Policy exclusions are not evaluated successes; ordinary schema invalidity remains +distinct from operational failure. Historical W/G/RC/F/J and failed probes remain +intact in their original scope. + +## Actual receiving and accessibility closure + +Three Mac drags delivered callbacks with exact comparisons: native JSON to the typed +JSON reader, Markdown to its subtype reader and Markdown to a generic String reader. +Earlier automation gestures delivered no event and were not counted as successes. +Mac plain TextEdit paste/save retained all 105 UTF-8 bytes. Rich RTF paste/save retained +font/colors, but TextEdit's rich save canonicalized a decomposed key; emitted/native- +decoded RTF is separately exact. Native JSON is the interchange authority. HTML rich +paste and standalone opening were observed independently. + +Fresh physical iPhone Share PDF offered `A4.pdf` without editing; the maintainer +confirmed local Files save/reopen, seven pages and the final LAST-CONTENT-SENTINEL. +The iPad simulator's actual Files-saved `A4.pdf` matched prepared bytes exactly and +independently decoded as seven pages with the sentinel. After Device Hub automation +stalled, the maintainer confirmed actual Files reopening. These manual observations +are separate from generated-file decode and automated API outcomes. Physical iPad +retains the approved simulator-only qualification policy. + +Native Safari's new annotated fixture opened nested target ancestors and focused the +exact value; missing targets focused the existing parent with the full missing path. +The script-free formatted fixture retained complete data and explicit value/parent +anchors. The maintainer reported all four interactive diagnostic VoiceOver checks +passed: labeled controls/disclosure states, nested value/diagnostic announcement, +missing-parent/path announcement, status/no keyboard trap. VoiceOver's runtime was +absent afterward. Audible speech is human-reported, not inferred from AX or Playwright; +formatted speech and exhaustive accessibility certification are outside this check. +Earlier no-script/print/manual navigation results remain source-bound history. + +## Representative calibration and finite policy + +Eighteen release-native observations cover six deterministic workloads. Median +rendering was 44.693 ms for 5,000-member formatted output and 71.459 ms for a +1,000-member interactive object with 128 coherent mapped diagnostics. The latter +emitted 980,689 bytes, charged 4,202,193 owned work units and peaked at 1,961,378 +logical retained bytes. Default 128 diagnostics passed; 129 refused. + +The review added 54 browser observations: the same six frozen HTML workloads, +three fresh contexts each in Chromium 151.0.7922.34, WebKit 26.5 and Firefox 153.0. +The Browser plugin was unavailable; the existing pinned Playwright 1.62.1 runtime +was reused. All loads, actual DOM bounds, expansion/collapse and literal-search +completion/focus checks passed with no unexpected request or script error. +5,000-member formatted output had 50,020 actual DOM nodes against a 120,032 bound; +the 128-mapped-diagnostic object had 29,128 against 64,512. Median load/DOM times +for that annotated object were 114.160, 223.408 and 232.024 ms respectively; median +search completion was 44.130, 110.151 and 66.949 ms. Public consumer receipts separately +record native preparation/RTF/PDF expansion and elapsed platform work, including the +physical representative seven-page export and historical background soft deadlines. + +These observations support the retained finite initial profiles in their measured +representative scope. They do not guarantee maximum-workload throughput, idle A/B, +hard RSS, physical responsiveness or browser/SDK/caller instruction quotas. Logical +retention and owned work remain separate. No profile, deadline, 49-metric meaning, +private optimization or 10% review trigger changed. Historical streaming and schema +performance dispositions remain retained, not relabeled as new idle-host passes. diff --git a/Documentation/Architecture/Release1.3/TransferExportIncrement.md b/Documentation/Architecture/Release1.3/TransferExportIncrement.md new file mode 100644 index 0000000..316d1ad --- /dev/null +++ b/Documentation/Architecture/Release1.3/TransferExportIncrement.md @@ -0,0 +1,36 @@ +# Four-format transfer/export increment + +This stacked feature starts at qualified renderer commit +`3c1e8a1a1bc8d03699665b8ab98f5ece7962aff7`, with observed main +`25fb04f747e583209b4c4511abf39e11ef5b4f80`. It preserves PR #21's renderer history +and the separate 1.2 train. No release, merge or protection change belongs here. + +The [export contract](../JSONHTMLExport.md) freezes the first working declarations, +finite initial policy, one-meter accounting, native exactness and framework boundary. +Compiler/backend probes are in `Tools/JSONHTMLExport/Probes`; the independent +five-product consumer is `Tools/JSONHTMLExport`. The export CI lane has deliberate +16-function debug/release discovery, 35 public consumer outcomes and six fixture +hashes. Native/safety/docs retain the 49 caps and 173 parameterized invocations. +Successful command receipts, generated artifacts and final source identities are +materialized outside Git in the return bundle. A failed or pending lane remains +open; this document does not declare final release qualification. + +Two review-driven deviations from planning assumptions are retained: transport +errors may wrap native parser failures, and SDK generic type export can negotiate +against statically declared hidden representations. Closure guards now enforce +selection independently of advertisement conditions. Exact hidden JSON export +fails; selected Markdown still serves a generic text request after guarded fallback. +This behavior is covered by a public runtime test, not inferred from a signature. + +The modern offscreen WebPage API compiled and produced selectable final-sentinel +content on one tall region page. Its existence does not qualify A4/Letter +pagination. No public PDF preparation method or paper default is exposed. The T0 +probe has an external 45-second watchdog; that is not production cancellation +qualification. RTF, PDF and receiving-app/mobile runtime gates remain incomplete. +The earlier maintainer-authorized physical iPad deferral is unchanged. + +A separate source-review pass examines exact bytes, cumulative growth, metadata, +selection, failure cleanup, actor boundaries, dependency inventory and CI counting. +Findings and dispositions are returned with the source-bound receipts. The full +56-row transfer ledger is separate from historical W/G/RC/renderer records; no +completed historical gate is rewritten to claim a new format passed. diff --git a/Documentation/CI/README.md b/Documentation/CI/README.md index 5ca45db..15091c0 100644 --- a/Documentation/CI/README.md +++ b/Documentation/CI/README.md @@ -63,7 +63,49 @@ distribution, architecture or libc claims. `python3 Scripts/ci.py --lane wasm` requires the matching Swift.org 6.4.0 compiler, SDK and WasmKit plus Node. It records cross-build and runtime outcomes separately. -It preserves all 192/173 root discoveries, independent corpus policies and 49 cap -outcomes; both hosts also execute the frozen public consumer. The +Node executes the complete 256-function/173-parameter portable root suites, +independent corpus policies and all 49 cap outcomes. Both hosts execute the frozen +public consumer; WasmKit retains its separately recorded 14-function consumer scope. The [WASI contract](../Architecture/WebAssembly.md) defines host/safety scopes. This local lane neither activates hosted CI nor substitutes for Apple/Linux gates. + +## JSONHTML export development lane + +`python3 Scripts/ci.py --lane export` requires 38 focused Swift Testing functions +in both configurations, 65 public consumer outcomes, eight artifacts, six pinned fixtures and three +Apple SDK declaration probes. It builds all five products in iPhoneOS and +simulator consumers. A bounded offscreen WebPage region experiment remains outside +the production API. The lane establishes native API/file transport and SDK builds, +not share sheets or receiving-editor UI. Explicit paginated PDF and simulator/device execution have separate materialized receipts. +Current root discovery is 270 functions on Mac (75 HTML) and 256 on Linux/WASI +(61 HTML). The focused export lane requires 38 functions on Mac. Ten Mac shared +functions, including independent PDF cancellation, run under Thread Sanitizer. All 49 cap checks +and the 173 baseline parameterized invocations remain required. + +## Structured Swift Testing receipts + +Swift 6.4's supported event stream records discovered, started and completed +function identities and parameterized case completions. CI checks these identities, +exact bundle cardinality and every baseline parameterized function's count (173 +invocations total), and rejects missing/duplicate/skipped/issue/malformed events. +Human console summaries are retained as diagnostics; observed hosted runs can +truncate those summaries even with exit zero. JSONL is split on LF so Unicode line +separators inside valid message strings are preserved. Nineteen harness regression +probes test these failures; they supplement the Swift Testing behavior suite. +This change does not reduce discovery, cases, sanitizer coverage or the 49 caps. + +Historically, the styled RTF increment added five portable functions and one native decode/style +function, retaining all existing cases. Mac root counts are 107/51/14/12/11/47; +Linux counts are 107/51/14/12/11/44. Optional `qualify_rtf_runtime.py` executes the +public consumer on isolated iPhone/iPad simulators; SDK compilation is separate +from those outcomes and from physical-device/receiver UI qualification. + +## Integrated 1.3 portable qualification + +The five-product WASI lane requires six exact bundles: JSON 107, schema 51, private +regex 12, streaming 14, patch 11 and portable HTML 61. Its public consumer requires +14 functions and six explicit HTML/export outcomes in both Node and WasmKit. All +173 parameter cases, 49 cap outcomes, pinned independent suites and rooted vectors +remain required. The first failed inherited expectations are retained; no blanket +nonzero/exit-zero replacement is used. Real receiving-app and physical-device UI +observations remain separate from automated SDK/API and portable execution. diff --git a/Examples/Consumer/Sources/Consumer/READMEExamples.swift b/Examples/Consumer/Sources/Consumer/READMEExamples.swift index 76036fe..d4dd19c 100644 --- a/Examples/Consumer/Sources/Consumer/READMEExamples.swift +++ b/Examples/Consumer/Sources/Consumer/READMEExamples.swift @@ -105,4 +105,15 @@ func runREADMEExamples() throws { initiallyExpandedDepth: 2, outlineDepth: 3 )).renderWithReport(value) precondition(interactiveHTML.statistics.copyJSONBytes > 0) + + let exportReport = try JSONExportDocument.prepareWithReport(value) + let markdownItem = try exportReport.result.selecting([.markdown]) + let markdownBytes = try markdownItem.data(for: .markdown) + precondition(exportReport.operation[.nodes] > 0 && !markdownBytes.isEmpty) + + let richItem = try JSONExportDocument.prepare(value, options: .init( + formats: [.json, .plainText, .rtf] + )).selecting([.rtf]) + let richBytes = try richItem.data(for: .rtf) + precondition(!richBytes.isEmpty) } diff --git a/Examples/Consumer/Sources/Consumer/main.swift b/Examples/Consumer/Sources/Consumer/main.swift index e3832a5..e107192 100644 --- a/Examples/Consumer/Sources/Consumer/main.swift +++ b/Examples/Consumer/Sources/Consumer/main.swift @@ -62,3 +62,20 @@ do { _ = try JSONHTMLRenderer(options: .init(limits: interactiveLimits, presenta catch let failure as JSONFailure { precondition(failure.category == .resource); refusedInteractive = true } precondition(refusedInteractive) print("JSONHTML_INTERACTIVE_PUBLIC_CONSUMER passed exact_metadata=1 cumulative_refusal=1") + +var diagnosticOptions = JSONHTMLRenderingOptions(presentation: .interactive) +diagnosticOptions.annotations = [ + .init(path: [.key("count")], severity: .information, message: "Caller selected the exact value"), + .init(path: [.key("missing"), .index(0)], severity: .error, message: "Missing instance value")] +requireSendable(diagnosticOptions.annotations) +let mappedDocument = try JSONParser().parseDocument(JSONWriter(options: .init(numberStyle: .preserved)).write(value)) +let diagnosticRenderer = JSONHTMLRenderer(options: diagnosticOptions) +let diagnosticReport = try diagnosticRenderer.renderStringWithReport(mappedDocument) +requireSendable(diagnosticReport) +precondition(diagnosticReport.statistics.annotations == 2) +precondition(diagnosticReport.statistics.operation[.nodes] == 3) +precondition(diagnosticReport.result.contains("Value source span:") && diagnosticReport.result.contains("Parent source span:")) +precondition(diagnosticReport.result.contains("Missing target; nearest existing parent")) +let unmappedDiagnosticHTML = try diagnosticRenderer.renderString(mappedDocument.replacingRoot(value)) +precondition(!unmappedDiagnosticHTML.contains("source span:")) +print("JSONHTML_ANNOTATION_PUBLIC_CONSUMER passed typed_targets=2 coherent_spans=2 replaced_spans=0") diff --git a/README.md b/README.md index 68ea248..7ab8d4e 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Choose the products your application needs: | **JSON** | Exact values, strict parsing/writing, native Codable, Pointer, presence, checked construction, source documents and diagnostics | | **JSONSchema** | Draft 2020-12 compilation/evaluation, offline registries, bounded regex, annotations and raw declaration inspection | | **JSONStreaming** | Pull syntax events and values, strict NDJSON, root-array elements and awaited output chunks | -| **JSONHTML** | Exact highlighted HTML, offline disclosures/search and native-byte copy/download (1.3 development) | +| **JSONHTML** | Exact HTML, offline exploration and immutable native transfer/export (1.3 development) | | **JSONPatch** | Staged RFC 6902 JSON Patch and RFC 7396 Merge Patch | `JSONSchema`, `JSONStreaming`, `JSONPatch` and `JSONHTML` build on `JSON`. The package-private @@ -370,8 +370,46 @@ scalar identity. Clipboard acceptance and manual copying depend on the browser; printing includes the full formatted JSON independently of disclosure state. Read the [formatted guide](Sources/JSONHTML/Documentation.docc/FormattedRendering.md) and [interactive guide](Sources/JSONHTML/Documentation.docc/InteractiveRendering.md) -for limits and browser qualifications. Annotations, transfer formats, RTF and PDF -preparation remain later increments. Published 1.2 versions do not include JSONHTML. +for limits and browser qualifications. Prepare immutable exact JSON, UTF-8 text, +Markdown and static HTML together, then explicitly select the offered format: + +```swift +let exportReport = try JSONExportDocument.prepareWithReport(value) +let markdownItem = try exportReport.result.selecting([.markdown]) +let markdownBytes = try markdownItem.data(for: .markdown) +precondition(exportReport.operation[.nodes] > 0 && !markdownBytes.isEmpty) +``` + +On Apple platforms, importing JSONHTML adds JSON's native Transferable conformance. +Only strict JSON imports; presentation formats export. Default preparation uses one +tab per level, LF and light static HTML, sharing one finite cumulative budget. +Read the [prepared export guide](Sources/JSONHTML/Documentation.docc/PreparedExport.md) +and [architecture contract](Documentation/Architecture/JSONHTMLExport.md) for format +selection, transport error wrapping and finite limits. Real receiving-app behavior +is qualified separately from API/file transport. Opt into styled RTF for prepared +items; its native decoded text preserves the pretty JSON body. Generation is +actor-independent and uses finite style/UTF-16 caps alongside shared core quotas. + +```swift +let richItem = try JSONExportDocument.prepare(value, options: .init( + formats: [.json, .plainText, .rtf] +)).selecting([.rtf]) +let richBytes = try richItem.data(for: .rtf) +precondition(!richBytes.isEmpty) +``` + +Prepare PDF explicitly on the main actor with `try await item.preparingPDF()` or +`try await item.preparingPDFWithReport()`. This uses the item's own static HTML; +success returns a new immutable item whose selected PDF transfers only completed +bytes. Defaults are paginated A4, 36-point margins and a light Menlo profile. +US Letter and an explicit continuous page are available. Finite limits and +cancellation cover owned operations; opaque SDK work and allocation are separate. +See the [PDF guide](Sources/JSONHTML/Documentation.docc/PDFPreparation.md). +Caller-supplied `JSONHTMLAnnotation` values attach diagnostics by exact typed path. +Rendering a coherent `JSONDocument` can show original value or parent UTF-8 byte +spans; unmapped or replaced roots invent none. Rendering does not execute schema +validation. See the [diagnostic guide](Sources/JSONHTML/Documentation.docc/DiagnosticAnnotations.md). +Published 1.2 versions do not include JSONHTML. ## Documentation and examples diff --git a/Scripts/ci.py b/Scripts/ci.py index 5897252..6ed3220 100644 --- a/Scripts/ci.py +++ b/Scripts/ci.py @@ -4,9 +4,10 @@ import argparse, datetime, hashlib, json, os, pathlib, re, subprocess, sys from archive_candidate import files from preflight import collect, collect_linux +from testing_events import read_events, ROOT_PARAMETER_CASES ROOT = pathlib.Path(__file__).resolve().parents[1] -SHARED_TESTS = 'independentCodecFacadesAndExactValuesCanBeSharedAcrossTasks|concurrentSchemaEvaluationsHaveExclusiveDynamicRegexAndAnnotationState|sharedRegexProgramHasExclusiveConcurrentSearchState|immutableIndexedObjectCanBeSharedAcrossTasks|qaSharedPayloadAndRootHaveIndependentConcurrentReadScopes|sha256InputCapacityAndConcurrentInvocationsRemainIndependent|sharedFormattedHTMLRendererHasIndependentConcurrentInvocations' +SHARED_TESTS = 'independentCodecFacadesAndExactValuesCanBeSharedAcrossTasks|concurrentSchemaEvaluationsHaveExclusiveDynamicRegexAndAnnotationState|sharedRegexProgramHasExclusiveConcurrentSearchState|immutableIndexedObjectCanBeSharedAcrossTasks|qaSharedPayloadAndRootHaveIndependentConcurrentReadScopes|sha256InputCapacityAndConcurrentInvocationsRemainIndependent|sharedFormattedHTMLRendererHasIndependentConcurrentInvocations|sharedAnnotatedDocumentRendererHasIndependentConcurrentInvocations|sharedExportPreparationsHaveIndependentConcurrentState|applePDFSimultaneousJobsHaveIndependentStateAndCancellation' R0_TESTS = 'readerProbe|indexProbes|duplicateRecognitionPrecedesValueAndClosingSyntax' def snapshot(): @@ -38,6 +39,14 @@ def commands(lane, output): ['swift', 'test', '-c', 'release', '--filter', 'qa'], ['swift', 'test', '-c', 'release', '--sanitize', 'address', '--scratch-path', '.build/ci-address', '--filter', 'qa'], ] + if lane == 'export': + return [ + ['python3', '-B', 'Scripts/public_check.py'], + ['python3', '-B', 'Scripts/verify_html_assets.py'], + ['swift', 'test', '--filter', 'export|Export|apple'], + ['swift', 'test', '-c', 'release', '--filter', 'export|Export|apple'], + ['python3', '-B', 'Scripts/qualify_json_export.py', '--output', str(output / 'apple')], + ] if lane == 'browser': return [ ['python3', '-B', 'Scripts/verify_html_assets.py'], @@ -50,6 +59,7 @@ def commands(lane, output): ['swift', 'package', 'dump-package'], ['swift', 'package', 'show-dependencies', '--format', 'json'], ['swift', 'build', '-c', 'release'], + ['python3', '-B', 'Scripts/verify_testing_events.py'], ['swift', 'test'], ['swift', 'test', '-c', 'release'], ['python3', '-B', 'Scripts/qualify_release_1_1.py', '--mode', 'ownership', '--output', str(output / 'ownership.json')], ['python3', '-B', 'Scripts/qualify_release_1_1.py', '--mode', 'reader', '--output', str(output / 'reader.json')], @@ -96,14 +106,16 @@ def materialized_outcomes(output): raise ValueError('Missing or failing rooted consumer outcomes') return {name: {'sha256': hashlib.sha256((output / name).read_bytes()).hexdigest()} for name in ['qualification.json', 'consumer.json', 'watchdog.json']} -def check_result(command, code, log): +def check_result(command, code, log, events=None): counts = [int(n) for n in re.findall(r'Test run with (\d+) tests? in[^\n]*passed', log)] passed = code == 0 + if events is not None: + counts = events['bundle_counts'] if command[:2] == ['swift', 'test']: - expected = [13] if 'Tools/WasmAudit' in command else [2, 3, 3, 11] if 'qa' in command else [7] if R0_TESTS in command else [3] if 'Tools/MCPReadiness' in command or 'Tools/MobileQualification' in command else [1] if 'Tools/Qualification' in command else [1, 1, 1, 4] if 'thread' in command else [11, 12, 14, 25, 51, 107] + expected = [38] if 'export|Export|apple' in command else [14] if 'Tools/WasmAudit' in command else [2, 3, 3, 11] if 'qa' in command else [7] if R0_TESTS in command else [3] if 'Tools/MCPReadiness' in command or 'Tools/MobileQualification' in command else [1] if 'Tools/Qualification' in command else [1, 1, 3 if sys.platform == 'linux' else 4, 4] if 'thread' in command else sorted([11, 12, 14, 61 if sys.platform == 'linux' else 75, 51, 107]) passed = passed and sorted(counts) == expected if '--filter' not in command and not any(x.startswith('Tools/') for x in command): - passed = passed and sum(int(n) for n in re.findall(r'with (\d+) test cases passed', log)) == 173 + passed = passed and (events['parameterized_functions'] == ROOT_PARAMETER_CASES if events is not None else sum(int(n) for n in re.findall(r'with (\d+) test cases passed', log)) == 173) if 'Tools/Qualification' in command: probes = re.findall(r'^CAP_PROBE (\w+) (\S+)$', log, re.MULTILINE) matrix = json.loads((ROOT / 'Documentation/Qualification/LimitsMatrix.json').read_text()) @@ -119,17 +131,19 @@ def check_result(command, code, log): passed = json.loads(log)['dependencies'] == [] if command[:2] == ['swift', 'run'] and 'Tools/MobileQualification' in command: passed = passed and all(line in log.splitlines() for line in ['PUBLIC_BEHAVIOR passed fixtures=8', 'STREAM_CANCELLATION passed', 'DISK_PERSISTENCE passed containers=3 rows=8', 'MOBILE_QUALIFICATION passed cases=3']) + if command[:3] == ['python3', '-B', 'Scripts/verify_testing_events.py']: + passed = passed and 'TESTING_EVENT_RECEIPTS passed cases=19' in log.splitlines() if command[:3] == ['python3', '-B', 'Scripts/verify_html_assets.py']: passed = passed and 'JSONHTML_ASSETS passed count=2' in log.splitlines() if 'BrowserFixtures' in command: - passed = passed and 'JSONHTML_BROWSER_FIXTURES passed count=8' in log.splitlines() + passed = passed and 'JSONHTML_BROWSER_FIXTURES passed count=10' in log.splitlines() if command[:3] == ['python3', '-B', 'Scripts/qualify_html_browser.py']: - passed = passed and 'JSONHTML_BROWSER passed engines=3 cases=37 skips=0' in log.splitlines() + passed = passed and 'JSONHTML_BROWSER passed engines=3 cases=46 skips=0' in log.splitlines() return passed, counts def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--lane', choices=['native', 'safety', 'docs', 'r0', 'qa', 'linux', 'linux-safety', 'wasm', 'browser'], required=True) + parser.add_argument('--lane', choices=['native', 'safety', 'docs', 'r0', 'qa', 'linux', 'linux-safety', 'wasm', 'browser', 'export'], required=True) parser.add_argument('--output') args = parser.parse_args() output = ROOT / (args.output or '.build/ci/' + args.lane) @@ -146,6 +160,10 @@ def save(): return 2 receipt['status'] = 'running' for index, command in enumerate(commands(args.lane, output)): + event_path = output / ('command-%02d.events.jsonl' % index) if command[:2] == ['swift', 'test'] else None + if event_path is not None: + event_path.unlink(missing_ok=True) + command = command + ['--event-stream-output-path', str(event_path.relative_to(ROOT))] print('Running ' + ' '.join(command), flush=True) log_path = output / ('command-%02d.log' % index) with log_path.open('w') as stream: @@ -157,11 +175,22 @@ def save(): code = None log = log_path.read_text(errors='replace').replace(str(ROOT), '.') log_path.write_text(log) + events = None + event_error = None try: - passed, counts = check_result(command, code, log) - except (KeyError, ValueError, TypeError): + if event_path is not None: + events = read_events(event_path.read_text()) + passed, counts = check_result(command, code, log, events=events) + except (OSError, KeyError, ValueError, TypeError) as error: passed, counts = False, [] + event_error = str(error) entry = {'command': [x.replace(str(ROOT), '.') for x in command], 'exit_code': code, 'status': 'passed' if passed else 'failed', 'log': log_path.relative_to(ROOT).as_posix(), 'log_sha256': hashlib.sha256(log_path.read_bytes()).hexdigest(), 'swift_testing_function_counts': counts, 'parameterized_case_invocations': sum(int(n) for n in re.findall(r'with (\d+) test cases passed', log))} + if events is not None: + entry['console_parameterized_case_invocations'] = entry['parameterized_case_invocations'] + entry['parameterized_case_invocations'] = events['parameter_case_invocations'] + entry['swift_testing_events'] = dict(events, path=event_path.relative_to(ROOT).as_posix(), sha256=hashlib.sha256(event_path.read_bytes()).hexdigest()) + if event_error is not None: + entry['event_error'] = event_error receipt['commands'].append(entry) print(json.dumps(entry), flush=True) if not passed: @@ -180,7 +209,7 @@ def save(): if receipt['status'] == 'passed' and args.lane == 'wasm': try: wasi = json.loads((output / 'probes/wasi.json').read_text()) - if wasi['status'] != 'passed' or wasi['root_counts'] != {'JSONTests':104,'JSONSchemaTests':51,'JSONRegexRuntimeTests':12,'JSONStreamingTests':14,'JSONPatchTests':11}: + if wasi['status'] != 'passed' or wasi['root_counts'] != {'JSONTests':107,'JSONSchemaTests':51,'JSONRegexRuntimeTests':12,'JSONStreamingTests':14,'JSONPatchTests':11,'JSONHTMLTests':61}: raise ValueError('Missing WASI qualification boundary') receipt['materialized_wasi'] = {'path':'probes/wasi.json','sha256':hashlib.sha256((output / 'probes/wasi.json').read_bytes()).hexdigest()} except (OSError,KeyError,ValueError,TypeError) as error: diff --git a/Scripts/qualify_html_browser.py b/Scripts/qualify_html_browser.py index e140bb8..f0ece25 100644 --- a/Scripts/qualify_html_browser.py +++ b/Scripts/qualify_html_browser.py @@ -13,7 +13,7 @@ def main(): result = subprocess.run([node, str(ROOT / 'Tools/JSONHTMLBrowser/browser.cjs'), str(ROOT / args.fixtures), str(output)], cwd=ROOT / 'Tools/JSONHTMLBrowser') if result.returncode: return result.returncode receipt = json.loads((output / 'browser.json').read_text()) - if receipt['status'] != 'passed' or len(receipt['engines']) != 3 or len(receipt['cases']) != 37 or any(c['status'] != 'passed' for c in receipt['cases']) or receipt['unexpectedSkips']: + if receipt['status'] != 'passed' or len(receipt['engines']) != 3 or len(receipt['cases']) != 46 or any(c['status'] != 'passed' for c in receipt['cases']) or receipt['unexpectedSkips']: return 1 return 0 if __name__ == '__main__': sys.exit(main()) diff --git a/Scripts/qualify_json_export.py b/Scripts/qualify_json_export.py new file mode 100644 index 0000000..77fd13c --- /dev/null +++ b/Scripts/qualify_json_export.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Native export and Apple compiler/API qualification; receiver/device gates remain separate.""" +import argparse, hashlib, json, os, pathlib, subprocess, sys +from archive_candidate import files +from preflight import collect + +ROOT = pathlib.Path(__file__).resolve().parents[1] + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', required=True) + args = parser.parse_args() + output = ROOT / args.output + output.mkdir(parents=True, exist_ok=True) + inputs = [{'path': p.relative_to(ROOT).as_posix(), 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()} for p in files()] + receipt = {'status': 'blocked', 'environment': collect(), 'source_inputs': inputs, + 'source_manifest_sha256': hashlib.sha256(json.dumps(inputs, sort_keys=True).encode()).hexdigest(), + 'commands': [], 'capability': 'four defaults, opt-in styled RTF and explicit paginated/continuous Apple PDF; API/file transport', + 'limitations': ['SDK builds are not simulator/device execution.', + 'No share-sheet, drag/drop or receiving-editor UI success is inferred.', + 'Opaque SDK allocation/work is not owned instruction or process RSS accounting.', + 'Native PDF text/geometry and RTF decoding do not establish recipient UI or physical-device behavior.']} + def run(name, command, timeout=900): + log = output / (name + '.log') + with log.open('w') as stream: + try: + result = subprocess.run(command, cwd=ROOT, stdout=stream, stderr=subprocess.STDOUT, timeout=timeout) + code = result.returncode + except subprocess.TimeoutExpired: + code = 124 + stream.write('\nEXTERNAL_PROBE_TIMEOUT\n') + entry = {'name': name, 'command': command, 'exit_code': code, + 'log': log.relative_to(ROOT).as_posix(), 'log_sha256': hashlib.sha256(log.read_bytes()).hexdigest()} + receipt['commands'].append(entry) + if code: raise ValueError(name + ' failed with exit ' + str(code)) + return log.read_text() + try: + if receipt['environment']['status'] != 'passed': raise ValueError('Required native preflight blocked') + fixture_root = ROOT / 'Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures' + registry = json.loads((fixture_root / 'registry.json').read_text()) + for fixture in registry['fixtures']: + path = fixture_root / pathlib.Path(fixture['path']).name + if hashlib.sha256(path.read_bytes()).hexdigest() != fixture['sha256']: raise ValueError('Fixture hash mismatch') + receipt['fixtures_verified'] = registry['fixtures'] + text = run('native-consumer', ['swift', 'run', '-c', 'release', '--package-path', 'Tools/JSONHTMLExport', + 'ExportConsumer', str(output / 'samples')]) + if 'JSONHTML_EXPORT_CONSUMER passed cases=65 artifacts=8 measurements=7' not in text.splitlines(): + raise ValueError('Changed native consumer cardinality') + consumer = json.loads((output / 'samples/consumer.json').read_text()) + if consumer['status'] != 'passed' or len(consumer['cases']) != 65 or not all(row['passed'] for row in consumer['cases']): + raise ValueError('Missing consumer outcomes') + receipt['consumer'] = consumer + if len(consumer['pdfReports']) != 3 or len(consumer['samples']) != 8: raise ValueError('Missing PDF artifacts/reports') + probe = ROOT / 'Tools/JSONHTMLExport/Probes/WebPageBackend.swift' + for sdk, target in [('macosx', 'arm64-apple-macos27.0'), ('iphoneos', 'arm64-apple-ios27.0'), + ('iphonesimulator', 'arm64-apple-ios27.0-simulator')]: + sdk_path = run(sdk + '-path', ['xcrun', '--sdk', sdk, '--show-sdk-path']).strip() + run(sdk + '-webpage-declarations', ['xcrun', 'swiftc', '-swift-version', '6', '-warnings-as-errors', + '-parse-as-library', '-sdk', sdk_path, '-target', target, '-typecheck', str(probe)]) + if sdk != 'macosx': + run(sdk + '-public-consumer', ['swift', 'build', '-c', 'release', '--package-path', 'Tools/JSONHTMLExport', + '--scratch-path', str(output / sdk), '--triple', target, '--sdk', sdk_path]) + binary = output / 'webpage-probe' + run('webpage-compile', ['xcrun', 'swiftc', '-swift-version', '6', '-warnings-as-errors', '-parse-as-library', + '-target', 'arm64-apple-macos27.0', str(probe), '-o', str(binary)]) + text = run('webpage-region-runtime', [str(binary), str(output / 'WebPage-region-probe.pdf')], timeout=45) + if 'finished=true' not in text or 'sentinel=true' not in text: raise ValueError('Incomplete region probe') + receipt['backend_probe'] = {'observation': text.strip(), 'production_qualified': False, + 'pagination': 'region capture only; no A4/Letter backend selected', + 'pdf_sha256': hashlib.sha256((output / 'WebPage-region-probe.pdf').read_bytes()).hexdigest()} + receipt['status'] = 'passed' + except (ValueError, OSError, subprocess.SubprocessError) as error: + receipt['status'] = 'failed' if receipt['commands'] else 'blocked' + receipt['error'] = str(error) + (output / 'receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') + print(json.dumps({k: v for k, v in receipt.items() if k not in ('source_inputs', 'consumer')}, indent=2)) + return 0 if receipt['status'] == 'passed' else 1 + +if __name__ == '__main__': sys.exit(main()) diff --git a/Scripts/qualify_rtf_runtime.py b/Scripts/qualify_rtf_runtime.py new file mode 100644 index 0000000..46c4685 --- /dev/null +++ b/Scripts/qualify_rtf_runtime.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Run the built public export consumer on owned iPhone/iPad simulators.""" +import argparse, datetime, hashlib, json, pathlib, subprocess, sys +from archive_candidate import files +from preflight import collect +ROOT = pathlib.Path(__file__).resolve().parents[1] + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--binary', required=True, help='Actual iPhoneSimulator ExportConsumer binary') + parser.add_argument('--output', required=True) + args = parser.parse_args() + binary = (ROOT / args.binary).resolve() + output = ROOT / args.output + output.mkdir(parents=True, exist_ok=True) + inputs = [{'path': p.relative_to(ROOT).as_posix(), 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()} for p in files()] + receipt = {'schema_version': 1, 'status': 'running', 'environment': collect(), + 'source_inputs': inputs, 'source_manifest_sha256': hashlib.sha256(json.dumps(inputs, sort_keys=True).encode()).hexdigest(), + 'git_head': subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT, text=True).strip(), + 'started_utc': datetime.datetime.now(datetime.timezone.utc).isoformat(), + 'commands': [], 'simulators': [], 'limitations': ['Native UIKit decoder and transport CLI; no share sheet/editor/physical-device UI claim.']} + def save(): (output / 'receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') + def run(name, command): + print('Running ' + name, flush=True) + try: + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=300) + code = result.returncode; text = result.stdout + result.stderr; stdout = result.stdout + except subprocess.TimeoutExpired as error: + code = 124; stdout = ''; text = str(error) + '\nEXTERNAL_SIMULATOR_TIMEOUT\n' + log = output / (name + '.log'); log.write_text(text.replace(str(ROOT), '.')) + receipt['commands'].append({'name': name, 'command': command, 'exit_code': code, + 'log': log.relative_to(ROOT).as_posix(), 'log_sha256': hashlib.sha256(log.read_bytes()).hexdigest()}) + save() + if code: raise RuntimeError(name + ' failed with exit ' + str(code)) + return stdout + save() + try: + if receipt['environment']['status'] != 'passed': raise FileNotFoundError('Required native preflight blocked') + if not binary.is_file(): raise FileNotFoundError('Missing simulator consumer binary') + receipt['binary_sha256'] = hashlib.sha256(binary.read_bytes()).hexdigest() + # The SwiftPM resource bundle remains beside the original binary. + inventory = json.loads(run('inventory', ['xcrun', 'simctl', 'list', 'devices', 'available', '--json']))['devices'] + runtime = 'com.apple.CoreSimulator.SimRuntime.iOS-27-0' + for family in ['iPhone', 'iPad']: + model = next((d for d in inventory.get(runtime, []) if d['name'].startswith(family) and d['isAvailable']), None) + if model is None: raise FileNotFoundError('No supported ' + family + ' simulator model/runtime') + device = run(family + '-create', ['xcrun', 'simctl', 'create', 'Swift-JSON-Export-' + family, model['deviceTypeIdentifier'], runtime]).strip() + try: + run(family + '-boot', ['xcrun', 'simctl', 'boot', device]) + run(family + '-bootstatus', ['xcrun', 'simctl', 'bootstatus', device, '-b']) + samples = output / family + text = run(family + '-consumer', ['xcrun', 'simctl', 'spawn', device, str(binary), str(samples)]) + if 'JSONHTML_EXPORT_CONSUMER passed cases=65 artifacts=8 measurements=7' not in text.splitlines(): + raise RuntimeError('Changed consumer discovery/cardinality') + consumer = json.loads((samples / 'consumer.json').read_text()) + if consumer['status'] != 'passed' or len(consumer['cases']) != 65 or not all(c['passed'] for c in consumer['cases']): + raise RuntimeError('Incomplete or failing consumer outcomes') + rtf = samples / 'Exact-JSON.rtf' + receipt['simulators'].append({'family': family, 'model': model['name'], 'runtime': runtime, + 'status': 'passed', 'cases': 65, 'artifacts': 8, 'rtf_sha256': hashlib.sha256(rtf.read_bytes()).hexdigest(), + 'pdf_sha256': {name: hashlib.sha256((samples / name).read_bytes()).hexdigest() for name in ['A4.pdf', 'Letter.pdf', 'Continuous.pdf']}, + 'consumer': consumer}) + save() + finally: + # Delete only the simulator created by this invocation. + run(family + '-delete', ['xcrun', 'simctl', 'delete', device]) + receipt['status'] = 'passed' + except FileNotFoundError as error: + receipt['status'] = 'blocked'; receipt['error'] = str(error) + except (OSError, RuntimeError, ValueError, KeyError, subprocess.TimeoutExpired) as error: + receipt['status'] = 'failed'; receipt['error'] = str(error) + receipt['source_inputs_unchanged'] = inputs == [{'path': p.relative_to(ROOT).as_posix(), 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()} for p in files()] + if not receipt['source_inputs_unchanged']: receipt['status'] = 'failed' + receipt['finished_utc'] = datetime.datetime.now(datetime.timezone.utc).isoformat() + save(); print('EXPORT_SIMULATORS ' + receipt['status']) + return 0 if receipt['status'] == 'passed' else 2 if receipt['status'] == 'blocked' else 1 + +if __name__ == '__main__': sys.exit(main()) diff --git a/Scripts/testing_events.py b/Scripts/testing_events.py new file mode 100644 index 0000000..fcad9c9 --- /dev/null +++ b/Scripts/testing_events.py @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Count completed Swift Testing ABI events, independently of human console summaries.""" +import collections +import json + +ROOT_PARAMETER_CASES = { + 'equivalentDecimalSpellings(text:)': 4, 'inclusiveAdmission(attempt:)': 3, + 'numberGrammarIsStrict(text:)': 10, 'exactDivisibilityUsesDecimalMathematics(a:b:expected:)': 6, + 'strictArrayIndexGrammar(text:)': 5, 'malformedUTF8(bytes:)': 4, + 'invalidPointerEscapes(text:)': 4, 'invalidCancellationConfiguration(quantum:)': 3, + 'strictMalformedInput(text:)': 10, 'negativeZeroHasOneIdentity(text:)': 3, + 'everyByteSplitUsesIdenticalGrammar(text:)': 4, 'malformedRootArrayNeverCompletes(text:)': 6, + 'streamEverySplitAgreesWithNativeParser(text:)': 3, + 'successfulAlternativesCannotSwallowRegexExhaustion(keyword:)': 3, + 'URIIdentityUsesSyntaxNormalizationWithoutUnicodeFolding(input:expected:)': 9, + 'malformedRegexGrammarIsACompilationFailure(pattern:)': 12, + 'unknownOrMisspelledPropertyNamesFailCompilation(pattern:)': 10, + 'nativeRegexScalarPortVectors(pattern:subject:expected:)': 10, + 'pinnedUnicodePropertiesAndIdentifierNames(pattern:subject:expected:)': 21, + 'scalarBacktrackingSemantics(pattern:subject:expected:)': 43, +} + + +def read_events(text): + tests = {} + suites = {} + started = collections.Counter() + ended = collections.Counter() + case_started = collections.Counter() + case_ended = collections.Counter() + runs = collections.Counter() + records = 0 + # JSONL delimiters are LF, not Unicode line separators inside valid strings. + for line in text.split('\n'): + if not line: + continue + row = json.loads(line) + records += 1 + payload = row['payload'] + kind = payload['kind'] + if row['kind'] == 'test': + if kind in ('function', 'suite'): + identifier = payload['id'] + if identifier in tests or identifier in suites: + raise ValueError('Duplicate test discovery') + (tests if kind == 'function' else suites)[identifier] = payload + elif row['kind'] == 'event': + if kind in ('issueRecorded', 'testSkipped'): + raise ValueError('Issue or skipped test in event stream') + if kind in ('runStarted', 'runEnded'): + runs[kind] += 1 + elif kind in ('testStarted', 'testEnded', 'testCaseStarted', 'testCaseEnded'): + identifier = payload['testID'] + if payload.get('iteration', 1) != 1: + raise ValueError('Unexpected repeated test iteration') + table = {'testStarted': started, 'testEnded': ended, + 'testCaseStarted': case_started, 'testCaseEnded': case_ended}[kind] + table[identifier] += 1 + identifiers = set(tests) + all_identifiers = identifiers | set(suites) + if not identifiers or set(started) != all_identifiers or set(ended) != all_identifiers: + raise ValueError('Missing discovered/start/completed function identity') + if any(started[k] != 1 or ended[k] != 1 for k in all_identifiers): + raise ValueError('Duplicate function or suite execution') + if any(not any(k.startswith(s + '/') or k.startswith(s + '.') for k in identifiers) for s in suites): + raise ValueError('Suite contains no discovered function') + if runs['runStarted'] == 0 or runs['runStarted'] != runs['runEnded']: + raise ValueError('Incomplete test run') + if case_started != case_ended: + raise ValueError('Incomplete parameterized case execution') + parameter_ids = {k for k, v in tests.items() if v.get('isParameterized', False)} + if set(case_ended) != parameter_ids: + raise ValueError('Parameterized discovery/completion mismatch') + modules = collections.Counter(k.split('.')[0] for k in identifiers) + parameters = {tests[k]['name']: case_ended[k] for k in parameter_ids} + if len(parameters) != len(parameter_ids): + raise ValueError('Ambiguous parameterized function name') + return {'event_records': records, 'module_function_counts': dict(sorted(modules.items())), + 'bundle_counts': sorted(modules.values()), 'function_count': len(identifiers), 'suite_count': len(suites), + 'parameter_case_invocations': sum(case_ended.values()), + 'parameterized_functions': dict(sorted(parameters.items()))} diff --git a/Scripts/verify_testing_events.py b/Scripts/verify_testing_events.py new file mode 100644 index 0000000..50c5600 --- /dev/null +++ b/Scripts/verify_testing_events.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Regression probes for the exact Swift Testing event receipt reader.""" +import collections +import json +from testing_events import read_events + + +def records(): + identifier = 'ProbeTests.value(text:)/Probe.swift:1:1' + return [ + {'kind': 'event', 'payload': {'kind': 'runStarted'}}, + {'kind': 'test', 'payload': {'kind': 'function', 'id': identifier, 'name': 'value(text:)', 'isParameterized': True}}, + *[{'kind': 'event', 'payload': {'kind': kind, 'testID': identifier}} for kind in + ['testStarted', 'testCaseStarted', 'testCaseEnded', 'testEnded']], + {'kind': 'event', 'payload': {'kind': 'runEnded', 'messages': [{'text': 'Unicode \u2028 \u0085 remains inside the JSON record.'}]}}, + ] + + +def encode(rows): + return ''.join(json.dumps(row, ensure_ascii=False) + '\n' for row in rows) + + +def main(): + original = records() + report = read_events(encode(original)) + assert report['bundle_counts'] == [1] and report['parameter_case_invocations'] == 1 + cases = 1 + for kind in ['testStarted', 'testCaseStarted', 'testCaseEnded', 'testEnded', 'runEnded']: + mutant = [r for r in original if r['payload']['kind'] != kind] + try: + read_events(encode(mutant)) + except ValueError: + cases += 1 + else: + raise AssertionError('Missing completion/discovery accepted: ' + kind) + for extra in [original[1], original[2], {'kind': 'event', 'payload': {'kind': 'issueRecorded'}}, + {'kind': 'event', 'payload': {'kind': 'testSkipped'}}]: + try: + read_events(encode(original + [extra])) + except ValueError: + cases += 1 + else: + raise AssertionError('Duplicate, issue or skip accepted') + try: + read_events(encode(original) + '{') + except ValueError: + cases += 1 + else: + raise AssertionError('Malformed event stream accepted') + suite = 'ProbeTests.Scope' + nested = json.loads(json.dumps(original)) + for row in nested: + payload = row['payload'] + for key in ('id', 'testID'): + if key in payload: payload[key] = suite + '/' + payload[key].split('.', 1)[1] + declaration = {'kind': 'test', 'payload': {'kind': 'suite', 'id': suite, 'name': 'Scope'}} + start = {'kind': 'event', 'payload': {'kind': 'testStarted', 'testID': suite}} + end = {'kind': 'event', 'payload': {'kind': 'testEnded', 'testID': suite}} + nested = nested[:1] + [declaration, start] + nested[1:-1] + [end] + nested[-1:] + report = read_events(encode(nested)) + assert report['function_count'] == 1 and report['suite_count'] == 1 and report['bundle_counts'] == [1] + cases += 1 + malformed = [ + [r for r in nested if r != start], [r for r in nested if r != end], + [r for r in nested if r != declaration], nested + [declaration], nested + [start], + nested + [{'kind': 'event', 'payload': {'kind': 'testCaseEnded', 'testID': suite}}], + nested + [{'kind': 'test', 'payload': {'kind': 'suite', 'id': 'ProbeTests.Empty', 'name': 'Empty'}}, + {'kind': 'event', 'payload': {'kind': 'testStarted', 'testID': 'ProbeTests.Empty'}}, + {'kind': 'event', 'payload': {'kind': 'testEnded', 'testID': 'ProbeTests.Empty'}}], + ] + for mutant in malformed: + try: read_events(encode(mutant)) + except ValueError: cases += 1 + else: raise AssertionError('Malformed suite discovery/completion accepted') + print('TESTING_EVENT_RECEIPTS passed cases=' + str(cases)) + return 0 + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/Scripts/wasm_support.py b/Scripts/wasm_support.py index dde4ac8..74efe58 100644 --- a/Scripts/wasm_support.py +++ b/Scripts/wasm_support.py @@ -6,9 +6,9 @@ ROOT = pathlib.Path(__file__).resolve().parents[1] SDK = 'swift-6.4.0-RELEASE_wasm' -COUNTS = {'JSONTests': 104, 'JSONSchemaTests': 51, 'JSONRegexRuntimeTests': 12, - 'JSONStreamingTests': 14, 'JSONPatchTests': 11} -GROUPS = {'core': 6, 'resources': 1, 'schema': 2, 'streaming': 3} +COUNTS = {'JSONTests': 107, 'JSONSchemaTests': 51, 'JSONRegexRuntimeTests': 12, + 'JSONStreamingTests': 14, 'JSONPatchTests': 11, 'JSONHTMLTests': 61} +GROUPS = {'core': 6, 'resources': 1, 'schema': 2, 'streaming': 3, 'html': 6} def main(): parser = argparse.ArgumentParser(description=__doc__) @@ -17,7 +17,7 @@ def main(): output = ROOT / args.output; output.mkdir(parents=True, exist_ok=True) result = {'schema_version': 1, 'status': 'running', 'target': 'wasm32-unknown-wasip1', 'sdk': SDK, 'commands': [], 'modules': [], 'root_counts': COUNTS, - 'wasmkit_scope': '13 public consumer functions, 49 caps, 20 rooted vectors, debug/release command parity', + 'wasmkit_scope': '14 public consumer functions, 49 caps, 20 rooted vectors, debug/release command parity', 'node_scope': 'complete debug/release root suites, caps, public consumers and independent qualification'} def save(): (output / 'wasi.json').write_text(json.dumps(result, indent=2) + '\n') @@ -54,7 +54,7 @@ def binary(scratch, config, name): run('sdk', [swift, 'sdk', 'list'], lambda c, t: c == 0 and SDK in t.splitlines()) run('wasmkit-version', [wasmkit, '--version']); run('node-version', [node, '--version']) manifest = json.loads(run('root-manifest', [swift, 'package', 'dump-package'])) - if manifest['dependencies'] or sorted(p['name'] for p in manifest['products']) != ['JSON', 'JSONPatch', 'JSONSchema', 'JSONStreaming']: + if manifest['dependencies'] or sorted(p['name'] for p in manifest['products']) != ['JSON', 'JSONHTML', 'JSONPatch', 'JSONSchema', 'JSONStreaming']: raise ValueError('Changed public products/dependencies') def invoke(engine, path, mode='command', arguments=(), repo=True, resources=True): if engine == 'node': @@ -79,7 +79,7 @@ def invoke(engine, path, mode='command', arguments=(), repo=True, resources=True modules[config, name] = binary(scratch, config, name) runner = binary(scratch, config, {'WasmAudit': 'WasmAuditTests', 'Qualification': 'LimitQualificationTests', 'MCPReadiness': 'MCPReadinessTests'}[name] + '-test-runner') for engine in ['node', 'wasmkit']: - text = run(engine + '-' + config + '-' + name + '-tests', invoke(engine, runner, 'tests'), tests({'WasmAudit':13, 'Qualification':1, 'MCPReadiness':3}[name])) + text = run(engine + '-' + config + '-' + name + '-tests', invoke(engine, runner, 'tests'), tests({'WasmAudit':14, 'Qualification':1, 'MCPReadiness':3}[name])) if name == 'Qualification': matrix = json.loads((ROOT / 'Documentation/Qualification/LimitsMatrix.json').read_text()) expected = {m['metric']: m['public_probe_outcome'] for m in matrix['metrics']} diff --git a/Sources/JSONHTML/Documentation.docc/DiagnosticAnnotations.md b/Sources/JSONHTML/Documentation.docc/DiagnosticAnnotations.md new file mode 100644 index 0000000..e236c4f --- /dev/null +++ b/Sources/JSONHTML/Documentation.docc/DiagnosticAnnotations.md @@ -0,0 +1,61 @@ +# Render caller diagnostics and source spans + +Attach explicitly supplied diagnostics without running schema validation inside the renderer. + +```swift +import Foundation +import JSON +import JSONHTML + +let document = try JSONParser().parseDocument(Data(#"{"count":9007199254740993}"#.utf8)) +var options = JSONHTMLRenderingOptions(presentation: .interactive) +options.annotations = [ + .init(path: [.key("count")], severity: .information, message: "Exact count"), + .init(path: [.key("missing"), .index(0)], severity: .error, message: "Required value is absent") +] +let renderer = JSONHTMLRenderer(options: options) +let report = try renderer.renderStringWithReport(document) +precondition(report.statistics.annotations == 2) +``` + +All four renderer methods accept either a JSON value or a JSONDocument. Configuration +and successful reports are Sendable. Annotations are caller diagnostics, with textual +information/warning/error severities. Empty lists make no claim about schema validity. +A caller importing JSONSchema can translate its issue paths explicitly; JSONHTML still +depends only on JSON and never catches arbitrary errors to render their descriptions. + +Paths retain decoded scalar identity and component types. A numeric object key differs +from an array index; composed and decomposed keys remain distinct. Existing values get +stable generated targets and accessible descriptions. Missing values are explicitly +marked missing, with the full attempted typed path and a link to their nearest existing +parent. Out-of-range indices and wrong container kinds are missing targets. Negative +indices are invalid configuration, even after an earlier missing component. + +Formatted rendering remains script-free. Its zero-width target anchors do not add text +to formatted JSON; a static caller-diagnostic section follows the complete body. +Interactive diagnostic links use the existing ancestor-opening/focus navigation. +Annotations never enter search, copied JSON, Pointer data or the complete print JSON +block. The separate diagnostic section remains visible in printed output. + +Only parser-owned coherent mappings produce source spans. These are zero-based, +half-open original UTF-8 byte intervals for values. Missing targets show the parent's +span, labeled as such, and never an invented child location. Root-only documents and +`replacingRoot` have no mapping, even when the replacement compares equal. Source +labels and original source bytes are not embedded or archived; line/column coordinates +are not inferred from generated formatting. + +`maxAnnotations` is an inclusive finite HTML capacity (default 128). Message and key +admission uses core string/key/aggregate text bounds. Their raw UTF-8 bytes and array +index digits also consume cumulative `diagnosticBytes` (balanced default 262,144). +Every complete attempted path uses `pointerTokens` and escaped RFC 6901 `pointerBytes` +capacities. Generated quoted labels, escaping, binding, exact comparisons, source-map +scans, traversal scratch, DOM growth and result copies share the existing invocation +meter. No source-map public facade creates a fresh allowance. Zero forbids the relevant +usage; UInt64.max capacities fail configuration. Existing 49 core metrics retain their +meanings and defaults. + +Messages and keys use visible JSON-style quoting with explicit C1 and bidi-control escapes before HTML escaping, so controls such as +NUL, CR and LF cannot disappear through HTML parsing. Payload never enters selectors, +IDs, executable code or raw markup. Profiles bound native owned work and logical +retention, not browser or caller instructions, process RSS or receiver behavior. +Failure/cancellation returns no successful partial result or report. diff --git a/Sources/JSONHTML/Documentation.docc/FormattedRendering.md b/Sources/JSONHTML/Documentation.docc/FormattedRendering.md index a37b139..ef52ccf 100644 --- a/Sources/JSONHTML/Documentation.docc/FormattedRendering.md +++ b/Sources/JSONHTML/Documentation.docc/FormattedRendering.md @@ -49,5 +49,6 @@ invocation, so an immutable renderer may be shared across tasks. This API formats values only. A source document can be rendered through its `root`; it does not imply preservation of source whitespace or source locations. Browser accessibility, printing and the complete supported-platform matrix need -their separate later qualification. Transfer/export, RTF and PDF remain future increments. See +their separate later qualification. Native transfer and prepared exports, including opt-in styled RTF, are documented +in . PDF remains a later increment. See for the separately selected interactive presentation. diff --git a/Sources/JSONHTML/Documentation.docc/JSONHTML.md b/Sources/JSONHTML/Documentation.docc/JSONHTML.md index 44a894c..2872566 100644 --- a/Sources/JSONHTML/Documentation.docc/JSONHTML.md +++ b/Sources/JSONHTML/Documentation.docc/JSONHTML.md @@ -11,7 +11,10 @@ The 1.3 development API provides formatted mode and an explicit interactive mode Interactive documents use native disclosures plus fixed offline controls for outline navigation, literal search, exact JSON/Pointer copy and UTF-8 downloads. A separate complete formatted block serves printing independently of collapsed state. -Annotations, transfer conformance, RTF and PDF preparation remain later increments. +Native JSON transfer and immutable prepared JSON/text/Markdown/static HTML are +available in this development increment, with opt-in styled RTF for prepared items. +Explicit main-actor PDF preparation uses the item’s own static HTML. Typed caller annotations attach to exact values or explicitly missing targets at their nearest existing parent. Coherent JSONDocument input adds original byte spans. See for format +selection, native text fidelity and finite RTF capacities. Every call owns one cumulative finite core budget through admission, token formatting, escaping, fixed assets and metadata emission and final Data or String copying. The @@ -24,6 +27,9 @@ browser heap, browser instruction quotas, process RSS or printing behavior. - - +- +- +- ### Rendering @@ -31,6 +37,7 @@ browser heap, browser instruction quotas, process RSS or printing behavior. - ``JSONHTMLRenderingOptions`` - ``JSONHTMLTheme`` - ``JSONHTMLPresentation`` +- ``JSONHTMLAnnotation`` ### Limits and reports @@ -39,3 +46,23 @@ browser heap, browser instruction quotas, process RSS or printing behavior. - ``JSONHTMLStatistics`` - ``JSONHTMLResourceMetric`` - ``JSONHTMLFailure`` + +### Prepared native export + +- ``JSONExportDocument`` +- ``JSONExportOptions`` +- ``JSONExportFormat`` +- ``JSONExportLimits`` +- ``JSONExportReport`` +- ``JSONExportResourceMetric`` +- ``JSONExportFailure`` + +### Explicit Apple PDF preparation + +- ``JSONPDFOptions`` +- ``JSONPDFLimits`` +- ``JSONPDFPaper`` +- ``JSONPDFLayout`` +- ``JSONPDFPreparationReport`` +- ``JSONPDFResourceMetric`` +- ``JSONPDFFailure`` diff --git a/Sources/JSONHTML/Documentation.docc/PDFPreparation.md b/Sources/JSONHTML/Documentation.docc/PDFPreparation.md new file mode 100644 index 0000000..d6693b5 --- /dev/null +++ b/Sources/JSONHTML/Documentation.docc/PDFPreparation.md @@ -0,0 +1,81 @@ +# Explicit PDF preparation + +Prepare complete PDF bytes before offering them to the transfer system. + +```swift +import JSON +import JSONHTML + +@MainActor func pdfForSharing(_ root: JSON) async throws -> JSONExportDocument { + let prepared = try JSONExportDocument.prepare(root) + let report = try await prepared.preparingPDFWithReport(options: .init( + paper: .usLetter, marginPoints: 36 + )) + return try report.result.selecting([.pdf]) +} +``` + +This second operation consumes only the item's own prepared, expanded, script-free +HTML. It does not retain or reformat the source JSON, accept arbitrary HTML, or use +an interactive document. Missing HTML fails. Pure synchronous preparation still +rejects `.pdf`; direct JSON transfer retains its four fixed defaults. Transfer +closures look up completed selected bytes and never start WebKit or mutate caches. +Success returns a new immutable item; failure leaves the original unchanged. + +The supported Apple backend uses per-job nonpersistent WebPage state with content +JavaScript and subresources disabled, sensors denied and external navigation refused. +Fixed client-world code awaits `document.fonts.ready`, applies the trusted print +profile and reads only bounded syntax runs. The job admits each owned run before +array growth and checks cancellation between individual DOM reads. Navigation +processing is bounded to eight events admitted under the same meter. Native emoji +fallback can produce a measured height outside a whole 16-point line grid; neither +mode assumes such a grid. Paginated output uses CoreText visible +text ranges and CoreGraphics page composition. It never clips one long inline span +across multiple regions: that prototype duplicated searchable text at page edges. + +Defaults are genuine A4 pages, equal 36-point margins, local Menlo 11-point text, +16-point lines, character wrapping, white paper and a fixed light syntax palette. +US Letter and margins from zero through 144 points are supported. Actual tabs use +6.6 points per cell times the prepared HTML tab width. There is no title/header, +footer, page number, outline, control or annotation in the PDF. Native font fallback +renders supplementary and bidirectional text. PDF text extraction may normalize +combining sequences or omit emoji; PDF is a presentation format. Use the unchanged +native JSON artifact for exact scalar identity, number lexemes and interchange. + +Continuous output must be selected with `layout: .continuous`. It captures one +measured WebPage region at the selected paper's width and makes one tall PDF page. +It is limited to 14,400 total points including margins; it never substitutes for +pagination. Normal paginated pages have the selected physical paper geometry. + +Each explicit PDF call has one cumulative noncopyable core meter. It admits HTML, +all existing artifacts including hidden formats, fixed-script/adapter work, native +text reservations, intermediate/final PDF bytes and immutable result copies. It +never resets a nested helper's work. Original representation and aggregate export +caps also constrain the result; replacing an existing PDF does not double its final +bundle count. A report exposes accepted input/capture/output bytes, page count, +backend, elapsed time and public owned-work/logical-retention statistics. PDF performs +no root admission (`nodes == 0`). Caller composition across separate calls remains +the caller's responsibility; no failure report licenses a budget-reset retry. + +Default inclusive policy is balanced core limits, 8 MiB HTML, 8 MiB aggregate +intermediate page PDFs, 8 MiB final PDF, 16 MiB final bundle, 64 pages and 14,400 +measured content points. Zero forbids usage. The default responsiveness deadline is +30 seconds, with a maximum of ten minutes. These are finite initial policies, +qualified with small and generated wrapped inputs, not hard RSS/throughput limits. +Opaque WebKit/CoreText/CoreGraphics instructions and allocations are outside owned +instruction accounting. Native buffers are size-checked after SDK return; the SDK +can allocate more memory before that check. Logical retention is not process RSS. + +Cancellation is checked in owned loops and between native stages/pages. A terminal +latch stops/detaches the invocation, cancels its worker/timer and ignores late native +results. The timeout does not await an uncooperative task-group loser. It is a soft +responsiveness policy: main-actor scheduling or synchronous native SDK work can +delay observation, and platform work can outlive the caller's return. Such work +cannot install a PDF. Concurrent jobs have separate WebPage, layout and meter state. + +macOS, iPhoneOS and iPhoneSimulator SDK evidence, simulator runtime outcomes, +independent PDF text/geometry checks and rendered-page inspection are recorded +separately in the materialized increment report. Share-sheet/drag/drop receiving-app +UI and physical-device PDF behavior are distinct, unqualified gates. Linux/WASI +retain pure exports and return `backendUnavailable` for explicit PDF preparation; +no platform fallback renderer or new dependency is introduced. diff --git a/Sources/JSONHTML/Documentation.docc/PreparedExport.md b/Sources/JSONHTML/Documentation.docc/PreparedExport.md new file mode 100644 index 0000000..17cb248 --- /dev/null +++ b/Sources/JSONHTML/Documentation.docc/PreparedExport.md @@ -0,0 +1,108 @@ +# Prepared export and Apple transfer + +Prepare exact JSON, pretty UTF-8 text, safely fenced Markdown and expanded static +HTML without executing a browser. Add `.rtf` explicitly for styled monospaced +rich text. Prepare PDF separately with the explicit main-actor operation in . + +```swift +import JSON +import JSONHTML + +let root = try JSONParser().parse(#"{"amount":9007199254740993,"state":null}"#) +let report = try JSONExportDocument.prepareWithReport(root) +let markdown = try report.result.selecting([.markdown]) +let bytes = try markdown.data(for: .markdown) +let cumulativeWork = report.operation[.workUnits] +``` + +The immutable item owns complete bytes and bounded filename metadata. Selection +changes advertisement and shares existing buffers; hidden formats remain available +for exact lookup and remain in aggregate accounting. Empty/unavailable selections +fail. Owned generation and result copies use one cumulative finite meter with one +root admission, checked growth and cancellation. Logical retention is not RSS. + +Defaults use one literal tab per level, LF and light static HTML. Native JSON and +text share the pretty body; `compactJSON` changes only JSON. Numeric lexemes, +insertion order and decoded scalar sequences are preserved. Original whitespace +and string escape spellings are not reconstructed. Markdown uses a fence longer +than any backtick run, and its body is unchanged. HTML remains script-free and +expanded, with no interactive controls, external assets or hidden JSON values. + +On Apple platforms importing JSONHTML enables the package's single JSON Transferable +conformance. Direct JSON, HTML, plain text and Markdown requests use fixed finite +profiles. Only JSON imports; the immutable wrapper is export-only. Prepared transfer +closures guard both advertisement and exact selection and never render PDF or +mutate a shared cache. Generic text subtype negotiation belongs to the system; +select a format explicitly when recipient choice matters. CoreTransferable wraps +some native parser errors; use JSONParser for structured diagnostics. + +RTF uses the same strict syntax tokens and cumulative meter as other formats. +Its independently decoded body equals pretty JSON, including quoted string roots, +LF, literal indentation tabs, insertion order, exact number lexemes and decoded +scalar sequences. Braces/backslashes are escaped, and supplementary scalars use +signed UTF-16 surrogate pairs. Fixed local Menlo 11-point styling distinguishes +keys, strings, numbers, booleans and null. Explicit paragraph tab stops use 6.6 +points per cell times `tabWidth`. RTF always uses a deterministic light palette; +`theme` affects HTML only. A receiver may substitute fonts or convert colors. +No attachment, executable link, external asset or native text renderer is used +in generation. Native decoding is qualification work outside owned quotas. + +```swift +let rich = try JSONExportDocument.prepare(root, options: .init( + formats: [.json, .plainText, .rtf], tabWidth: 4 +)) +let richTextItem = try rich.selecting([.rtf]) +let richTextBytes = try richTextItem.data(for: .rtf) +``` + +The default inclusive limits are 8 MiB per artifact, 16 MiB aggregate, 4 KiB title, +128 bytes filename, 1,000,000 RTF color runs, 8,388,608 decoded RTF UTF-16 units +and balanced core/HTML limits. Each RTF artifact has its own run/unit ceilings; +all formats share core work/output/retention and aggregate prepared bytes. Tab +positions must fit the RTF signed 32-bit parameter, measured in twips. Zero forbids usage. These initial +policy values are not process or recipient memory/CPU guarantees. Each generated +artifact, overlapping construction buffer and owned copy is charged. Filename +components reject controls, separators, dot components and bidi controls. + +The public export consumer exercises API/file transport, native decoding and PDF +geometry/text independently of receiving-app and physical-device behavior. Failed +explicit PDF requests install no artifact and advertise no capability. The original +T0 tall-region probe is historical evidence; it never established pagination. + +## Receiving applications + +Offer an explicitly selected immutable item to SwiftUI with an explicit preview: + +```swift +import SwiftUI +import JSONHTML + +@MainActor func sharePrepared(_ item: JSONExportDocument) throws -> some View { + let selected = try item.selecting([.json]) + return ShareLink("Share JSON", item: selected, + preview: SharePreview("Exact offline JSON")) +} +``` + +Actual Mac ShareLink PDF/Copy bytes matched the prepared PDF. Physical iPhone +share/open checks passed for the representative generated JSON/Markdown/PDF input. +These observations do not promise every receiving app chooses the same format. +Explicit Copy JSON and Copy Markdown routes should select their exact artifacts. + +TextEdit plain paste/save preserved the native pretty-body UTF-8 bytes. Its saved +rich RTF retained Menlo 11 and token colors but normalized a decomposed key to +composed Unicode. HTML rich paste also carries the standalone document heading +and follows the receiver's formatting choices. Presentation exports are export-only; +retain native JSON for exact scalar identity and interchange. Native emitted RTF +text/attributes are tested separately from these receiving-editor transformations. +See the [receiving qualification scope](../../../Documentation/Architecture/Release1.3/ReceivingQualification.md). + +### Selected-format filenames + +Prepared selection also governs the provider-level filename. Apple's item provider +collects one suggested name and can consult a hidden representation's metadata +before loading the selected bytes. JSONHTML uses the first advertised format in +its fixed preference order for that hidden metadata. Thus a PDF-only selection +advertises PDF with a .pdf name; Markdown-only selection uses .md. Explicit typed +exports retain their format-specific filenames, including when several formats +are available. Selection never prepares or substitutes another artifact. diff --git a/Sources/JSONHTML/JSONExportDocument.swift b/Sources/JSONHTML/JSONExportDocument.swift new file mode 100644 index 0000000..5b618c2 --- /dev/null +++ b/Sources/JSONHTML/JSONExportDocument.swift @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON + +/// Successful native preparation and its cumulative accounting. +public struct JSONExportReport { + /// The complete immutable result; failures return no partial document. + public let result: Result + /// Shared admission, traversal, output and peak logical retention counts. + public let operation: JSONOperationStatistics + /// Exact byte counts for each prepared artifact. + public let representationBytes: [JSONExportFormat: UInt64] + /// Sum of all prepared artifact bytes, including subsequently hidden formats. + public let preparedBytes: UInt64 +} +extension JSONExportReport: Sendable where Result: Sendable {} + +/// Prepared immutable bytes, with per-item advertisement and no renderer, cache or source root. +/// +/// Native JSON preserves insertion order, decoded scalar identity and original +/// numeric lexemes when present. Presentation formats are export-only. Preparation +/// uses one cumulative meter. Transport and receiving-app work are outside it. +public struct JSONExportDocument: Sendable { + private let artifacts: [JSONExportFormat: Data] + private let maximumRepresentationBytes: UInt64 + private let maximumPreparedBytes: UInt64 + /// Exact artifact identifiers retained by this item, including hidden artifacts. + public let availableFormats: Set + /// Exact artifact identifiers offered for this item; subtype matching is system policy. + public let advertisedFormats: Set + /// Validated filename component; extensions are added per representation. + public let filenameStem: String + + private init(artifacts: [JSONExportFormat: Data], advertised: Set, filename: String, representationLimit: UInt64, preparedLimit: UInt64) { + maximumRepresentationBytes = representationLimit; maximumPreparedBytes = preparedLimit + self.artifacts = artifacts; availableFormats = Set(artifacts.keys) + advertisedFormats = advertised; filenameStem = filename + } + + /// Prepares all requested native artifacts atomically without executing a browser. + /// - Parameters: + /// - value: Exact root admitted once for the entire preparation. + /// - options: Finite capacities and explicit format/presentation choices. + /// - Returns: An immutable prepared item. + /// - Throws: Configuration, resource, unsupported format or cancellation failure. + public static func prepare(_ value: JSON, options: JSONExportOptions = .init()) throws -> Self { + try prepareWithReport(value, options: options).result + } + + /// Prepares requested native artifacts with one cumulative successful report. + /// - Parameters: + /// - value: Exact root admitted once across all formats. + /// - options: Finite capacities and explicit format/presentation choices. + /// - Returns: Complete artifacts and logical owned-operation accounting. + /// - Throws: Configuration, resource, unsupported format or cancellation failure. + public static func prepareWithReport(_ value: JSON, options: JSONExportOptions = .init()) throws -> JSONExportReport { + var meter = try JSONWorkMeter(limits: options.limits.core.intersection(options.htmlLimits.core), domain: .writing) + return try prepare(value, options: options, meter: &meter) + } + + /// Returns an existing exact artifact without rendering or content-type negotiation. + /// - Parameters: + /// - format: The exact identifier to look up, even if hidden from advertisement. + /// - Returns: Immutable owned bytes; subsequent caller copies are caller work. + /// - Throws: A bounded unavailable-format failure. + public func data(for format: JSONExportFormat) throws -> Data { + guard let data = artifacts[format] else { throw JSONExportFailure.unavailableFormat(format) } + return data + } + + // Some SDK transport helpers negotiate against the static representation list. + // Conditions govern advertisement; guard the closure too so hidden bytes cannot escape. + package func transferData(for format: JSONExportFormat) throws -> Data { + guard advertisedFormats.contains(format) else { throw JSONExportFailure.unavailableFormat(format) } + return try data(for: format) + } + + /// Returns a value advertising only the selected existing artifacts. + /// + /// Existing immutable buffers are shared without regeneration. Hidden artifacts + /// remain available and remain in the preparation's aggregate accounting. + /// - Parameters: + /// - formats: Nonempty subset of available formats. + /// - Returns: A new item with the exact per-item advertisement. + /// - Throws: Empty or unavailable selection failure. + public func selecting(_ formats: Set) throws -> Self { + guard !formats.isEmpty else { throw JSONExportFailure.invalidConfiguration } + for format in JSONExportFormat.allCases where formats.contains(format) && !availableFormats.contains(format) { + throw JSONExportFailure.unavailableFormat(format) + } + return .init(artifacts: artifacts, advertised: formats, filename: filenameStem, representationLimit: maximumRepresentationBytes, preparedLimit: maximumPreparedBytes) + } + + package static func prepare(_ value: JSON, options: JSONExportOptions, meter: inout JSONWorkMeter) throws -> JSONExportReport { + let retainedAtEntry = meter.used(.retainedBytes) + do { return try prepareBody(value, options: options, meter: &meter) } + catch { + meter.recordFailure(error) + try meter.release(meter.used(.retainedBytes) - retainedAtEntry) + throw error + } + } + + private static func prepareBody(_ value: JSON, options: JSONExportOptions, meter: inout JSONWorkMeter) throws -> JSONExportReport { + try meter.checkpoint(); try options.limits.validate(); try options.htmlLimits.validate() + guard !options.formats.isEmpty, (0...8).contains(options.indentation), (1...8).contains(options.tabWidth) else { + throw JSONExportFailure.invalidConfiguration + } + if options.formats.contains(.pdf) { throw JSONExportFailure.preparationRequired(.pdf) } + let titleBytes = try metadata(options.title, filename: false, limit: options.limits.maxTitleBytes, meter: &meter) + _ = try metadata(options.filenameStem, filename: true, limit: options.limits.maxFilenameBytes, meter: &meter) + try JSONAdmission.value(value, meter: &meter) + var artifacts: [JSONExportFormat: Data] = [:] + var counts: [JSONExportFormat: UInt64] = [:] + var aggregate: UInt64 = 0 + + func install(_ bytes: [UInt8], as format: JSONExportFormat, meter: inout JSONWorkMeter) throws { + try JSONExportBuffer.require(.representationBytes, UInt64(bytes.count), options.limits.maxRepresentationBytes) + aggregate = try JSONExportBuffer.adding(.preparedBytes, aggregate, UInt64(bytes.count), options.limits.maxPreparedBytes) + // Each artifact is a logical owned copy even when Foundation internally shares storage. + try meter.retain(48); try meter.work() + for _ in bytes { try meter.work(); try meter.retain(1) } + artifacts[format] = Data(bytes); counts[format] = UInt64(bytes.count) + try meter.checkpoint() + } + + let needsPretty = options.formats.contains(.plainText) || options.formats.contains(.markdown) + || (options.formats.contains(.json) && !options.compactJSON) + if needsPretty { + let body = try writing(value, indentation: options.indentation, options: options, meter: &meter) + if options.formats.contains(.json) && !options.compactJSON { try install(body, as: .json, meter: &meter) } + if options.formats.contains(.plainText) { try install(body, as: .plainText, meter: &meter) } + if options.formats.contains(.markdown) { + var longest: UInt64 = 0, run: UInt64 = 0 + for byte in body { + try meter.work() + run = byte == 96 ? try JSONExportBuffer.adding(.representationBytes, run, 1, options.limits.maxRepresentationBytes) : 0 + longest = max(longest, run) + } + let fence = max(3, try JSONExportBuffer.adding(.representationBytes, longest, 1, options.limits.maxRepresentationBytes)) + var markdown = JSONExportBuffer(limit: options.limits.maxRepresentationBytes) + for _ in 0.. (html: Data, bytes: UInt64, previousPDFBytes: UInt64, representationLimit: UInt64, preparedLimit: UInt64) { + guard let html = artifacts[.html] else { throw JSONPDFFailure.missingHTML } + var total: UInt64 = 0 + for data in artifacts.values { total = try pdfAdd(.preparedBytes, total, UInt64(data.count), UInt64(Int.max) - 1) } + return (html, total, UInt64(artifacts[.pdf]?.count ?? 0), maximumRepresentationBytes, maximumPreparedBytes) + } + // Package-only installation of a qualified artifact; no public injection API. + package func addingPDF(_ pdf: Data) -> Self { + var copy = artifacts; copy[.pdf] = pdf + return .init(artifacts: copy, advertised: advertisedFormats.union([.pdf]), filename: filenameStem, + representationLimit: maximumRepresentationBytes, preparedLimit: maximumPreparedBytes) + } + + private static func writing(_ value: JSON, indentation: Int?, options: JSONExportOptions, meter: inout JSONWorkMeter) throws -> [UInt8] { + var cursor = JSONWritingCursor(value: value, options: .init(limits: options.limits.core, + indentation: indentation, numberStyle: .preserved, indentationUnit: options.indentationUnit)) + var buffer = JSONExportBuffer(limit: options.limits.maxRepresentationBytes) + while let byte = try cursor.next(meter: &meter) { try buffer.append(byte, meter: &meter) } + return buffer.bytes + } + + private static func metadata(_ text: String, filename: Bool, limit: UInt64, meter: inout JSONWorkMeter) throws -> UInt64 { + let metric: JSONExportResourceMetric = filename ? .filenameBytes : .titleBytes + var count: UInt64 = 0 + // Reject dot components, separators, controls and bidi controls in suggested names. + if filename && (text.isEmpty || text == "." || text == "..") { throw JSONExportFailure.invalidConfiguration } + for byte in text.utf8 { + try meter.work(); count = try JSONExportBuffer.adding(metric, count, 1, limit) + guard byte >= 32 && byte != 127, !filename || (byte != 47 && byte != 92 && byte != 58) else { + throw JSONExportFailure.invalidConfiguration + } + try meter.retain(1) + } + for scalar in text.unicodeScalars { + try meter.work() + guard !(0x80...0x9f).contains(scalar.value), + !filename || (!(0x202a...0x202e).contains(scalar.value) && !(0x2066...0x2069).contains(scalar.value)) else { + throw JSONExportFailure.invalidConfiguration + } + } + return count + } +} + +package struct JSONExportBuffer { + package var bytes: [UInt8] = [] + package let limit: UInt64 + package static func require(_ metric: JSONExportResourceMetric, _ attempted: UInt64, _ limit: UInt64) throws { + guard attempted <= limit else { throw JSONExportFailure.limitExceeded(metric, limit: limit, attempted: attempted) } + } + package static func adding(_ metric: JSONExportResourceMetric, _ value: UInt64, _ amount: UInt64, _ limit: UInt64) throws -> UInt64 { + let (next, overflow) = value.addingReportingOverflow(amount) + guard !overflow else { throw JSONExportFailure.counterOverflow(metric) } + try require(metric, next, limit); return next + } + package mutating func append(_ byte: UInt8, meter: inout JSONWorkMeter) throws { + try meter.work(); _ = try Self.adding(.representationBytes, UInt64(bytes.count), 1, limit) + try meter.charge(.outputBytes); try meter.retain(1); bytes.append(byte) + } + package mutating func append(_ bytes: S, meter: inout JSONWorkMeter) throws where S.Element == UInt8 { + for byte in bytes { try append(byte, meter: &meter) } + } +} diff --git a/Sources/JSONHTML/JSONExportOptions.swift b/Sources/JSONHTML/JSONExportOptions.swift new file mode 100644 index 0000000..03041ce --- /dev/null +++ b/Sources/JSONHTML/JSONExportOptions.swift @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON + +/// Exact lookup identifiers for prepared artifacts, independent of MIME subtype matching. +public enum JSONExportFormat: String, CaseIterable, Hashable, Sendable { + /// Strict native JSON; the only importable transfer representation. + case json + /// The pretty JSON body, including quotes around string roots. + case plainText + /// A safely fenced JSON code block with the unchanged pretty body. + case markdown + /// Expanded, highlighted, script-free standalone HTML. + case html + /// Styled monospaced rich text with the unchanged pretty JSON body; explicit opt-in. + case rtf + /// Explicitly prepared PDF; added only after successful Apple PDF preparation. + case pdf + + /// The filename extension associated with this exact artifact format. + public var filenameExtension: String { + switch self { + case .json: "json" + case .plainText: "txt" + case .markdown: "md" + case .html: "html" + case .rtf: "rtf" + case .pdf: "pdf" + } + } +} + +/// Finite initial export policy, measured separately from process memory and transport work. +public struct JSONExportLimits: Sendable { + /// One cumulative meter across admission, formatting, wrapping, HTML and owned copies. + public var core: JSONLimits + /// Inclusive bytes in any one complete representation. + public var maxRepresentationBytes: UInt64 + /// Inclusive sum of all prepared artifacts, including formats hidden by selection. + public var maxPreparedBytes: UInt64 + /// Inclusive syntax color runs in each RTF artifact, including the initial run. + public var maxRTFStyleRuns: UInt64 + /// Inclusive UTF-16 code units in the decoded RTF body, including whitespace. + public var maxRTFUTF16Units: UInt64 + /// Inclusive UTF-8 title bytes before HTML escaping. + public var maxTitleBytes: UInt64 + /// Inclusive UTF-8 filename stem bytes, excluding the fixed extension. + public var maxFilenameBytes: UInt64 + + /// Creates checked finite export limits. Zero forbids the corresponding usage. + /// - Parameters: + /// - core: Shared cumulative core quotas and capacities. + /// - maxRepresentationBytes: Inclusive bytes per artifact. + /// - maxPreparedBytes: Inclusive aggregate artifact bytes. + /// - maxTitleBytes: Inclusive plain title bytes. + /// - maxFilenameBytes: Inclusive filename component bytes. + /// - maxRTFStyleRuns: Inclusive RTF color runs; one million by default. + /// - maxRTFUTF16Units: Inclusive decoded RTF units; eight million by default. + public init(core: JSONLimits = .balanced, maxRepresentationBytes: UInt64 = 8_388_608, + maxPreparedBytes: UInt64 = 16_777_216, maxTitleBytes: UInt64 = 4_096, + maxFilenameBytes: UInt64 = 128, maxRTFStyleRuns: UInt64 = 1_000_000, + maxRTFUTF16Units: UInt64 = 8_388_608) { + self.core = core; self.maxRepresentationBytes = maxRepresentationBytes + self.maxPreparedBytes = maxPreparedBytes; self.maxTitleBytes = maxTitleBytes + self.maxFilenameBytes = maxFilenameBytes + self.maxRTFStyleRuns = maxRTFStyleRuns; self.maxRTFUTF16Units = maxRTFUTF16Units + } + package func validate() throws { + try core.validate() + guard [maxRepresentationBytes, maxPreparedBytes, maxTitleBytes, maxFilenameBytes, maxRTFStyleRuns, maxRTFUTF16Units] + .allSatisfy({ $0 < UInt64(Int.max) }) else { throw JSONExportFailure.invalidConfiguration } + } +} + +/// Immutable preparation choices copied into an invocation; no global mutable defaults exist. +public struct JSONExportOptions: Sendable { + /// Requested complete artifacts; unsupported formats fail the entire preparation. + public var formats: Set + /// Bounded filename component, independent of JSON keys and the title. + public var filenameStem: String + /// Bounded plain title used only in HTML presentation. + public var title: String + /// Indentation units per level, from zero through eight. + public var indentation: Int + /// Literal tabs by default; this does not change JSONWriter defaults. + public var indentationUnit: JSONIndentationUnit + /// Visual HTML/RTF tab width, from one through eight; JSON bytes are unaffected. + public var tabWidth: Int + /// Deterministic light HTML by default. + public var theme: JSONHTMLTheme + /// Compact native JSON only; text and Markdown retain pretty formatting. + public var compactJSON: Bool + /// Finite shared core and export capacities. + public var limits: JSONExportLimits + /// HTML-specific ceilings; its core limits intersect the export core without resetting work. + public var htmlLimits: JSONHTMLLimits + + /// Creates preparation options with four native artifacts and one tab per level. + /// - Parameters: + /// - formats: Requested exact artifact identifiers. + /// - filenameStem: Safe bounded filename component without separators or controls. + /// - title: Plain bounded title without control characters. + /// - indentation: Unit count per nesting level, zero through eight. + /// - indentationUnit: Literal spaces or tabs. + /// - tabWidth: HTML/RTF tab display width, one through eight. + /// - theme: HTML color policy. + /// - compactJSON: Whether native JSON alone should omit pretty whitespace. + /// - limits: Shared core and export limits. + /// - htmlLimits: Additional HTML generation limits. + public init(formats: Set = [.json, .plainText, .markdown, .html], + filenameStem: String = "JSON", title: String = "JSON", indentation: Int = 1, + indentationUnit: JSONIndentationUnit = .tabs, tabWidth: Int = 4, + theme: JSONHTMLTheme = .light, compactJSON: Bool = false, + limits: JSONExportLimits = .init(), htmlLimits: JSONHTMLLimits = .init()) { + self.formats = formats; self.filenameStem = filenameStem; self.title = title + self.indentation = indentation; self.indentationUnit = indentationUnit; self.tabWidth = tabWidth + self.theme = theme; self.compactJSON = compactJSON; self.limits = limits; self.htmlLimits = htmlLimits + } +} + +/// Export-specific inclusive capacity identifiers; core failures retain their native resource detail. +public enum JSONExportResourceMetric: String, Sendable { + /// Bytes in one representation or its construction buffer. + case representationBytes + /// Sum of all prepared artifact bytes. + case preparedBytes + /// Syntax color runs in one RTF artifact, including the initial run. + case rtfStyleRuns + /// UTF-16 code units in the decoded RTF body. + case rtfUTF16Units + /// RTF tab position in twips, bounded by the format's signed 32-bit parameter. + case rtfTabPosition + /// Plain UTF-8 title bytes. + case titleBytes + /// Plain UTF-8 filename component bytes. + case filenameBytes +} + +/// Bounded export failures containing no untrusted text or partial successful document. +public enum JSONExportFailure: Error, Sendable, Equatable { + /// Empty selection, invalid limits, indentation or unsafe metadata. + case invalidConfiguration + /// A reserved or unsupported artifact is unavailable in this implementation. + case unsupportedFormat(JSONExportFormat) + /// PDF requires a separately qualified explicit preparation operation. + case preparationRequired(JSONExportFormat) + /// Exact artifact lookup or selection requested an absent artifact. + case unavailableFormat(JSONExportFormat) + /// An inclusive export ceiling failed before growth. + case limitExceeded(JSONExportResourceMetric, limit: UInt64, attempted: UInt64) + /// Checked export counter addition overflowed. + case counterOverflow(JSONExportResourceMetric) +} diff --git a/Sources/JSONHTML/JSONHTMLAnnotation.swift b/Sources/JSONHTML/JSONHTMLAnnotation.swift new file mode 100644 index 0000000..040d451 --- /dev/null +++ b/Sources/JSONHTML/JSONHTMLAnnotation.swift @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON + +/// A caller-supplied diagnostic addressed by exact typed JSON location. +/// +/// Rendering does not validate schemas. An empty list makes no validity claim. +/// Negative array indices fail at rendering entry, including after a missing component. +public struct JSONHTMLAnnotation: Sendable { + /// Human-readable severity; it does not change or establish validation results. + public enum Severity: String, Sendable { + /// Informational caller diagnostic. + case information + /// Warning supplied by the caller. + case warning + /// Error supplied by the caller, independent of renderer operational failures. + case error + } + /// Exact keys and array indices; numeric keys remain keys. + public let path: [JSONLocationComponent] + /// The caller's diagnostic classification. + public let severity: Severity + /// Plain scalar text, escaped visibly and admitted under finite diagnostic/text limits. + public let message: String + /// Creates a diagnostic without traversing a root or allocating a renderer. + /// - Parameters: + /// - path: Typed target; a missing target is identified at its nearest existing parent. + /// - severity: Caller diagnostic classification, displayed as text. + /// - message: Plain text; no arbitrary Error description is obtained automatically. + public init(path: [JSONLocationComponent], severity: Severity, message: String) { + self.path = path; self.severity = severity; self.message = message + } +} + +// Borrow caller annotations by ordinal; do not copy payloads or retain roots in output. +package struct JSONHTMLBoundAnnotation { + package let index: Int + package let prefixCount: Int + package var ordinal: UInt64 = 0 + package var span: JSONSourceSpan? +} +private enum AnnotationStep { + case node(JSON, JSONLocationComponent?) + case pop +} + +extension JSONHTMLRenderer { + // Every admission, selection, comparison and map scan uses the enclosing writing meter. + package func bindAnnotations(_ value: JSON, document: JSONDocument?, meter: inout JSONWorkMeter) throws -> [JSONHTMLBoundAnnotation] { + try JSONHTMLDocumentBuffer.require(.annotations, UInt64(options.annotations.count), options.limits.maxAnnotations) + guard !options.annotations.isEmpty else { return [] } + if let map = document?.sourceMap, let bytes = document?.sourceBytes { + try meter.require(.sourceMapEntries, UInt64(map.entries.count)) + try meter.require(.inputBytes, UInt64(bytes.count)) + } + var records: [JSONHTMLBoundAnnotation] = [] + for (index, annotation) in options.annotations.enumerated() { + try meter.work() + try meter.require(.pointerTokens, UInt64(annotation.path.count)) + try JSONAdmission.text(annotation.message, meter: &meter) + for _ in annotation.message.utf8 { try meter.work(); try meter.charge(.diagnosticBytes) } + var pathBytes: UInt64 = 0 + for component in annotation.path { + try meter.work() + // Count the full RFC 6901 spelling, including failed suffixes, before binding. + pathBytes = try meter.adding(.pointerBytes, pathBytes, 1) + switch component { + case .key(let key): + try JSONAdmission.text(key.string, key: true, meter: &meter) + for byte in key.string.utf8 { + try meter.work(); try meter.charge(.diagnosticBytes) + pathBytes = try meter.adding(.pointerBytes, pathBytes, byte == 126 || byte == 47 ? 2 : 1) + } + case .index(let index): + guard index >= 0 else { throw JSONHTMLFailure.invalidConfiguration } + var number = index + repeat { + try meter.work(); try meter.charge(.diagnosticBytes) + pathBytes = try meter.adding(.pointerBytes, pathBytes, 1); number /= 10 + } while number > 0 + } + } + var selected = value, prefix = 0 + for component in annotation.path { + try meter.work() + let next: JSON? + switch (selected, component) { + case (.object(let object), .key(let key)): next = try object.lookup(key.string, meter: &meter) + case (.array(let array), .index(let index)): + // JSON itself admits nil literals; a ternary can wrap JSON.null in Optional. + if index < array.count { next = array[index] } else { next = nil } + default: next = nil + } + guard let next else { break } + selected = next; prefix += 1 + } + try meter.retain(64); records.append(.init(index: index, prefixCount: prefix)) + } + var path: [JSONLocationComponent] = [], steps: [AnnotationStep] = [] + func push(_ step: AnnotationStep, meter: inout JSONWorkMeter) throws { + try meter.retain(JSONLogicalWeights.frame); steps.append(step) + } + try push(.node(value, nil), meter: &meter) + var ordinal: UInt64 = 0 + while let step = steps.popLast() { + try meter.release(JSONLogicalWeights.frame); try meter.work() + switch step { + case .pop: path.removeLast(); try meter.release(JSONLogicalWeights.frame) + case .node(let node, let component): + if let component { + try meter.retain(JSONLogicalWeights.frame); path.append(component) + try push(.pop, meter: &meter) + } + for index in records.indices { + try meter.work() + let record = records[index], annotation = options.annotations[record.index] + guard record.prefixCount == path.count else { continue } + var equal = true + for part in path.indices { + try meter.work() + switch (path[part], annotation.path[part]) { + case (.key(let a), .key(let b)): equal = try JSONComparison.textEquals(a.string, b.string, meter: &meter) + case (.index(let a), .index(let b)): equal = a == b + default: equal = false + } + if !equal { break } + } + if equal { + records[index].ordinal = ordinal + if let map = document?.sourceMap { + records[index].span = try map.span(at: path, meter: &meter) + } + } + } + ordinal = try JSONHTMLDocumentBuffer.adding(.renderedJSONNodes, ordinal, 1, options.limits.maxRenderedJSONNodes) + switch node { + case .object(let object): + for member in object.reversed() { try meter.work(); try push(.node(member.value, .key(member.key)), meter: &meter) } + case .array(let array): + for index in array.indices.reversed() { try meter.work(); try push(.node(array[index], .index(index)), meter: &meter) } + default: break + } + } + } + return records + } + + package func hasAnnotations(at ordinal: UInt64, in records: [JSONHTMLBoundAnnotation], meter: inout JSONWorkMeter) throws -> Bool { + for record in records { try meter.work(); if record.ordinal == ordinal { return true } } + return false + } + package func annotationDescription(at ordinal: UInt64, in records: [JSONHTMLBoundAnnotation], output: inout JSONHTMLDocumentBuffer, meter: inout JSONWorkMeter) throws { + var started = false + for record in records { + try meter.work() + guard record.ordinal == ordinal else { continue } + try output.append(started ? " jh-a-" : " aria-describedby=\"jh-a-", meter: &meter) + try output.decimal(UInt64(record.index), meter: &meter); started = true + } + if started { try output.append("\"", meter: &meter) } + } + package func annotationSection(_ records: [JSONHTMLBoundAnnotation], output: inout JSONHTMLDocumentBuffer, meter: inout JSONWorkMeter) throws { + output.setAnnotations(UInt64(records.count)) + guard !records.isEmpty else { return } + try output.addDOM(16, meter: &meter) + try output.append("

Caller diagnostics

Supplied annotations; rendering does not establish schema validity. Source spans are zero-based half-open UTF-8 byte ranges.

    ", meter: &meter) + for record in records { + try meter.work(); try output.addDOM(32, meter: &meter) + let annotation = options.annotations[record.index] + let missing = record.prefixCount != annotation.path.count + try output.append("
  1. ", meter: &meter) + try output.append(annotation.severity.rawValue, meter: &meter) + try output.append(": ", meter: &meter) + try output.append(missing ? "Missing target; nearest existing parent" : "Target value", meter: &meter) + try output.append("

    Attempted typed path: Root", meter: &meter) + for component in annotation.path { + try meter.work(); try output.addDOM(2, meter: &meter) + switch component { + case .key(let key): + try output.append(" / key ", meter: &meter) + try annotationText(key.string, output: &output, meter: &meter) + case .index(let index): + try output.append(" / index [", meter: &meter); try output.decimal(UInt64(index), meter: &meter) + try output.append("]", meter: &meter) + } + } + try output.append("

    Message: ", meter: &meter) + try annotationText(annotation.message, output: &output, meter: &meter) + try output.append("

    ", meter: &meter) + if let span = record.span { + try output.append(missing ? "

    Parent source span: " : "

    Value source span: ", meter: &meter) + try output.decimal(span.start, meter: &meter); try output.append("..<", meter: &meter) + try output.decimal(span.end, meter: &meter); try output.append(" bytes

    ", meter: &meter) + } + try output.append("
  2. ", meter: &meter) + } + try output.append("
", meter: &meter) + } + private func annotationText(_ text: String, output: inout JSONHTMLDocumentBuffer, meter: inout JSONWorkMeter) throws { + // Visible JSON-style quoting plus explicit C1/bidi-control spelling prevents + // presentation normalization/spoofing; exact target identity uses the original path. + try output.escaped(34, meter: &meter) + for scalar in text.unicodeScalars { + try meter.work() + let value = scalar.value + if value < 32 || (127...159).contains(value) || (0x202a...0x202e).contains(value) || (0x2066...0x2069).contains(value) { + try output.append("\\u", meter: &meter) + for shift in [12,8,4,0] { + try meter.work() + let digit = UInt8((value >> shift) & 15) + try output.appendByte(digit < 10 ? digit + 48 : digit + 87, meter: &meter) + } + } else if value == 34 || value == 92 { + try output.escaped(92, meter: &meter); try output.escaped(UInt8(value), meter: &meter) + } else { + try meter.work(4); try meter.retain(4) + let spelling = String(scalar) + for byte in spelling.utf8 { try output.escaped(byte, meter: &meter) } + try meter.release(4) + } + } + try output.escaped(34, meter: &meter) + } +} diff --git a/Sources/JSONHTML/JSONHTMLInteractive.swift b/Sources/JSONHTML/JSONHTMLInteractive.swift index 3b31cda..b47aa00 100644 --- a/Sources/JSONHTML/JSONHTMLInteractive.swift +++ b/Sources/JSONHTML/JSONHTMLInteractive.swift @@ -2,7 +2,7 @@ import JSON extension JSONHTMLRenderer { - package func renderInteractive(_ value: JSON, meter: inout JSONWorkMeter) throws -> JSONHTMLDocumentBuffer { + package func renderInteractive(_ value: JSON, annotations: [JSONHTMLBoundAnnotation], meter: inout JSONWorkMeter) throws -> JSONHTMLDocumentBuffer { let writing = JSONWritingOptions(limits: options.limits.core, indentation: options.indentation, numberStyle: .preserved, indentationUnit: options.indentationUnit) var copy = try nativeBytes(value, options: writing, meter: &meter) @@ -75,6 +75,7 @@ extension JSONHTMLRenderer { try meter.release(UInt64(pointer.count)); pointer = [] try output.append("\"", meter: &meter) if container && level < options.initiallyExpandedDepth { try output.append(" open", meter: &meter) } + try annotationDescription(at: id, in: annotations, output: &output, meter: &meter) try output.append(container ? ">" : ">", meter: &meter) try label(component, output: &output, meter: &meter, searchable: true) switch node { @@ -137,7 +138,9 @@ extension JSONHTMLRenderer { outline = [] try output.append("", meter: &meter) } - try output.append("
", meter: &meter)
+        try output.append("", meter: &meter)
+        try annotationSection(annotations, output: &output, meter: &meter)
+        try output.append("
", meter: &meter)
         try highlighted(value, options: writing, output: &output, meter: &meter)
         try output.append("
\"'&" + let message = "
\r\n\u{0}"\u{7f}\u{85}\u{202e}" + let root = JSON.object(try JSONObject(members: [JSONMember(key, value: .null)])) + for mode in [JSONHTMLPresentation.formatted, .interactive] { + let html = try annotated([issue([.key(JSONKey(key))], message)], mode: mode).renderString(root) + let text = try diagnostic(0, in: html) + #expect(!text.contains("")) + #expect(text.contains("<img") && text.contains("\\u000d\\u000a\\u0000")) + #expect(text.contains("&quot;")) + #expect(text.contains("\\u007f\\u0085\\u202e")) + #expect(text.contains("~1/a\\u0000\\u000d\\u000a</script>")) + #expect(!text.utf8.contains(0) && !text.utf8.contains(13)) + #expect(text.contains("data-missing=\"false\"")) + } + let empty = try annotated([]).renderString(.null) + #expect(!empty.contains("Caller diagnostics") && !empty.contains("schema validity")) +} + +@Test func annotationHTMLDocumentSpansAreCoherentAndReplacementInventsNone() throws { + let bytes = Data(" \n{\"é\":9007199254740993,\"e\u{301}\": [true,null]} \n".utf8) + let document = try JSONParser().parseDocument(bytes, source: "private-source-not-archived") + let paths: [[JSONLocationComponent]] = [[], [.key("é")], [.key("é"), .index(1)], [.key("é"), .index(9)]] + let renderer = annotated(paths.map { issue($0) }) + let html = try renderer.renderString(document) + // Known byte intervals independently derived from the exact input bytes. + let scalarStart = try #require(bytes.range(of: Data("9007199254740993".utf8))).lowerBound + let scalar = try diagnostic(1, in: html) + #expect(scalar.contains("Value source span: \(scalarStart)..<\(scalarStart+16) bytes")) + let nullStart = try #require(bytes.range(of: Data("null".utf8))).lowerBound + #expect(try diagnostic(2, in: html).contains("Value source span: \(nullStart)..<\(nullStart+4) bytes")) + let arrayStart = try #require(bytes.firstIndex(of: 91)), arrayEnd = try #require(bytes.firstIndex(of: 93)) + 1 + let parentText = try diagnostic(3, in: html) + #expect(parentText.contains("Parent source span: \(arrayStart)..<\(arrayEnd) bytes")) + #expect(!html.contains("private-source-not-archived")) + #expect(!html.contains("sourceBytes") && !html.contains("line 1")) + for unmapped in [JSONDocument(root: document.root), document.replacingRoot(document.root), document.replacingRoot(.null)] { + #expect(try !renderer.renderString(unmapped).contains("source span:")) + } +} + +@Test func annotationHTMLPublicDocumentOverloadsRetainValueFunctionShapes() throws { + let renderer = annotated([issue([])]) + let bytes: (JSON) throws -> Data = renderer.render + let text: (JSON) throws -> String = renderer.renderString + let byteReport: (JSON) throws -> JSONHTMLRenderReport = renderer.renderWithReport + let textReport: (JSON) throws -> JSONHTMLRenderReport = renderer.renderStringWithReport + let docBytes: (JSONDocument) throws -> Data = renderer.render + let docText: (JSONDocument) throws -> String = renderer.renderString + let docByteReport: (JSONDocument) throws -> JSONHTMLRenderReport = renderer.renderWithReport + let docTextReport: (JSONDocument) throws -> JSONHTMLRenderReport = renderer.renderStringWithReport + let document = JSONDocument(root: .null) + #expect(try bytes(.null) == docBytes(document)) + #expect(try Array(text(.null).utf8) == Array(docText(document).utf8)) + #expect(try byteReport(.null).statistics == docByteReport(document).statistics) + #expect(try textReport(.null).statistics == docTextReport(document).statistics) +} + +@Test func annotationHTMLCountCapsAreInclusiveAndZeroIsMeaningful() throws { + let annotations = [issue([]), issue([])] + for count: UInt64 in [1,2,3] { + var limits = JSONHTMLLimits(); limits.maxAnnotations = count + if count < 2 { + #expect(throws: JSONHTMLFailure.limitExceeded(.annotations, limit: 1, attempted: 2)) { try annotated(annotations, limits: limits).render(.null) } + } else { #expect(try annotated(annotations, limits: limits).renderWithReport(.null).statistics.annotations == 2) } + } + var limits = JSONHTMLLimits(); limits.maxAnnotations = 0 + #expect(try annotated([], limits: limits).renderWithReport(.null).statistics.annotations == 0) + #expect(throws: JSONHTMLFailure.self) { try annotated([issue([])], limits: limits).render(.null) } + limits.maxAnnotations = UInt64.max + #expect(throws: JSONHTMLFailure.invalidConfiguration) { try annotated([], limits: limits).render(.null) } + #expect(throws: JSONHTMLFailure.counterOverflow(.annotations)) { try JSONHTMLDocumentBuffer.adding(.annotations, UInt64.max, 1, UInt64.max) } +} + +@Test func annotationHTMLBoundsMessagesAndFailedPathSuffixesCumulatively() throws { + let annotations = [issue([.key("~/"), .index(123)], "message"), issue([], "other")] + let report = try annotated(annotations).renderWithReport(.null) + #expect(report.statistics.operation[.diagnosticBytes] == 17) // 7+2+3+5, both annotations and full failed path. + for cap: UInt64 in [16,17,18] { + var limits = JSONHTMLLimits(); limits.core[.diagnosticBytes] = cap + if cap < 17 { #expect(throws: JSONFailure.self) { try annotated(annotations, limits: limits).render(.null) } } + else { #expect(try annotated(annotations, limits: limits).render(.null) == report.result) } + } + for (metric, cap) in [(JSONResourceMetric.pointerTokens, UInt64(1)), (.pointerBytes, 8), (.keyBytes, 1), (.stringBytes, 6)] { + var limits = JSONHTMLLimits(); limits.core[metric] = cap + #expect(throws: JSONFailure.self) { try annotated(annotations, limits: limits).render(.null) } + } + // Invalid suffixes cannot hide behind the first missing component. + #expect(throws: JSONHTMLFailure.invalidConfiguration) { try annotated([issue([.key("absent"), .index(-1)])]).render(.null) } +} + +@Test func annotationHTMLSharesAdmissionAndFinalMaterializationBudgets() throws { + let root = try JSONParser().parse(#"{"a":[1,2]}"#) + for mode in [JSONHTMLPresentation.formatted, .interactive] { + let annotations = [issue([.key("a"), .index(1)])] + let renderer = annotated(annotations, mode: mode) + let report = try renderer.renderWithReport(root), text = try renderer.renderStringWithReport(root) + #expect(report.result == Data(text.result.utf8) && report.statistics == text.statistics) + #expect(report.statistics.operation[.nodes] == 4 && report.statistics.renderedJSONNodes == 4) + #expect(report.statistics.operation[.retainedBytes] == report.statistics.documentBytes) + for metric in [JSONResourceMetric.workUnits, .outputBytes, .retainedBytes] { + let boundary = metric == .retainedBytes ? report.statistics.operation.peakLogicalRetainedBytes : report.statistics.operation[metric] + for cap in [boundary-1,boundary,boundary+1] { + var limits = JSONHTMLLimits(); limits.core[metric] = cap + if cap < boundary { #expect(throws: JSONFailure.self) { try annotated(annotations, mode: mode, limits: limits).render(root) } } + else { #expect(try annotated(annotations, mode: mode, limits: limits).render(root) == report.result) } + } + } + } +} + +@Test func annotationHTMLBindingFailureCleansScratchAndLatchesMeter() throws { + var meter = try JSONWorkMeter(limits: .balanced, domain: .writing); try meter.retain(17) + let renderer = annotated([issue([]), issue([.key("missing"), .index(-1)])]) + #expect(throws: JSONHTMLFailure.invalidConfiguration) { try renderer.render(.null, meter: &meter) } + #expect(meter.used(.retainedBytes) == 17) + let work = meter.used(.workUnits) + #expect(throws: JSONHTMLFailure.invalidConfiguration) { try renderer.render(.null, meter: &meter) } + #expect(meter.used(.workUnits) == work) + try meter.release(17) +} + +@Test func annotationHTMLCancellationInBindingAndSourceScansIsTerminal() async throws { + let root = try JSONParser().parse(#"{"é":[1,2,3],"é":false}"#) + let document = try JSONParser().parseDocument(JSONWriter().write(root)) + for threshold: UInt64 in [64,256] { + try await Task { + var limits = JSONLimits.balanced; limits[.cancellationQuantum] = 1 + var meter = try JSONWorkMeter(limits: limits, domain: .writing); try meter.retain(17) + meter.checkpointObserver = { work in if work >= threshold { withUnsafeCurrentTask { $0?.cancel() } } } + let renderer = annotated(Array(repeating: issue([.key("é")], String(repeating: "x", count: 100)), count: 8)) + #expect(throws: CancellationError.self) { try renderer.render(root, document: document, meter: &meter) } + #expect(meter.used(.retainedBytes) == 17 && meter.used(.outputBytes) == 0) + let work = meter.used(.workUnits) + #expect(throws: CancellationError.self) { try renderer.render(root, meter: &meter) } + #expect(meter.used(.workUnits) == work) + try meter.release(17) + }.value + } + // Rendezvous inside a real source-map helper after earlier binding has consumed work. + let wide = JSON.array(Array(repeating: .null, count: 500)) + let mapped = try JSONParser().parseDocument(JSONWriter().write(wide)) + try await Task { + var limits = JSONLimits.balanced; limits[.cancellationQuantum] = 1 + var meter = try JSONWorkMeter(limits: limits, domain: .writing); try meter.retain(17) + try JSONAdmission.value(wide, meter: &meter) + let renderer = annotated([issue([.index(499)])]) + let records = try renderer.bindAnnotations(wide, document: nil, meter: &meter) + try meter.release(UInt64(records.count) * 64) + let earlierWork = meter.used(.workUnits) + meter.checkpointObserver = { work in if work >= earlierWork + 4 { withUnsafeCurrentTask { $0?.cancel() } } } + let map = try #require(mapped.sourceMap) + #expect(throws: CancellationError.self) { try map.span(at: [.index(499)], meter: &meter) } + #expect(meter.used(.workUnits) >= earlierWork + 4) + #expect(meter.used(.retainedBytes) == 17) + #expect(throws: CancellationError.self) { try renderer.render(wide, document: mapped, meter: &meter) } + #expect(meter.used(.retainedBytes) == 17) + try meter.release(17) + }.value +} + +@Test func sharedAnnotatedDocumentRendererHasIndependentConcurrentInvocations() async throws { + let document = try JSONParser().parseDocument(Data(#"{"é":1,"é":[2,3]}"#.utf8)) + let renderer = annotated([issue([.key("é")]), issue([.key("é"), .index(1)]), issue([.key("absent")])]) + let expected = try renderer.renderWithReport(document) + try await withThrowingTaskGroup(of: JSONHTMLRenderReport.self) { group in + for _ in 0..<16 { group.addTask { try renderer.renderWithReport(document) } } + var count = 0 + for try await actual in group { #expect(actual.result == expected.result && actual.statistics == expected.statistics); count += 1 } + #expect(count == 16) + } +} + +@Test func annotationHTMLDeepWideAndDuplicateBindingsKeepStableOrdinals() throws { + var root: JSON = .null, path: [JSONLocationComponent] = [] + for _ in 0..<60 { root = .array([root]); path.append(.index(0)) } + let annotations = [issue(path), issue(path, "second")] + let html = try annotated(annotations).renderString(root) + #expect(html.contains("id=\"jh-n-60\"") && html.contains("aria-describedby=\"jh-a-0 jh-a-1\"")) + #expect(try diagnostic(1, in: html).contains("data-target=\"jh-n-60\"")) + let wide = JSON.array(Array(repeating: .null, count: 500)) + #expect(try diagnostic(0, in: annotated([issue([.index(499)])]).renderString(wide)).contains("data-target=\"jh-n-500\"")) +} + +@Test func annotationHTMLSourceAdmissionHonorsConfiguredMappingAndInputCaps() throws { + let document = try JSONParser().parseDocument(Data(#"{"a":[1,2]}"#.utf8)) + for metric in [JSONResourceMetric.sourceMapEntries, .inputBytes] { + let boundary = metric == .sourceMapEntries ? UInt64(document.sourceMap!.entries.count) : UInt64(document.sourceBytes!.count) + for cap in [boundary-1,boundary,boundary+1] { + var limits = JSONHTMLLimits(); limits.core[metric] = cap + if cap < boundary { #expect(throws: JSONFailure.self) { try annotated([issue([.key("a")])], limits: limits).render(document) } } + else { #expect(try annotated([issue([.key("a")])], limits: limits).renderString(document).contains("Value source span:")) } + } + } +} diff --git a/Tests/JSONHTMLTests/ExportTests.swift b/Tests/JSONHTMLTests/ExportTests.swift new file mode 100644 index 0000000..b177baf --- /dev/null +++ b/Tests/JSONHTMLTests/ExportTests.swift @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import Testing +@testable import JSONHTML +#if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) +import CoreTransferable +import UniformTypeIdentifiers +#endif + +@Test func exportSixRootsAndEmptyContainersAreComplete() throws { + for source in ["null", "true", "1.00E+2", #""é😀""#, "[]", "{}", "[1,null]", #"{"a":false}"#] { + let value = try JSONParser().parse(source) + let report = try JSONExportDocument.prepareWithReport(value) + let item = report.result + #expect(item.availableFormats == [.json, .plainText, .markdown, .html]) + #expect(item.availableFormats == item.advertisedFormats) + #expect(try JSONParser().parse(item.data(for: .json)) == value) + #expect(try item.data(for: .json) == item.data(for: .plainText)) + #expect(report.operation[.nodes] == (source == "[1,null]" ? 3 : source == #"{"a":false}"# ? 2 : 1)) + #expect(report.preparedBytes == report.representationBytes.values.reduce(0, +)) + } +} + +@Test func exportExactScalarsOrderAndLexemesUseIndependentByteAnswers() throws { + let value = try JSONParser().parse(#"{"é":9007199254740993,"é":-0.00e+2,"":1.2300E-7,"0":"é","__proto__":true}"#) + let expected = "{\n\t\"é\": 9007199254740993,\n\t\"e\u{301}\": -0.00e+2,\n\t\"\": 1.2300E-7,\n\t\"0\": \"e\u{301}\",\n\t\"__proto__\": true\n}" + let item = try JSONExportDocument.prepare(value) + #expect(try item.data(for: .json) == Data(expected.utf8)) + #expect(try item.data(for: .markdown) == Data(("```json\n" + expected + "\n```\n").utf8)) + #expect(try JSONExportDocument.prepare(.number(42)).data(for: .json) == Data("42".utf8)) +} + +@Test func exportPlainStringRootRemainsQuotedAndMarkdownFenceIsSafe() throws { + let value: JSON = .string("`````` ~~~~~ & 😀") + let item = try JSONExportDocument.prepare(value) + let body = #""`````` ~~~~~ & 😀""# + #expect(try item.data(for: .plainText) == Data(body.utf8)) + #expect(try item.data(for: .markdown) == Data(("```````json\n" + body + "\n```````\n").utf8)) +} + +@Test func exportCompactJSONDoesNotChangePrettyTextOrMarkdown() throws { + let item = try JSONExportDocument.prepare(.array([.null]), options: .init(compactJSON: true)) + #expect(try item.data(for: .json) == Data("[null]".utf8)) + #expect(try item.data(for: .plainText) == Data("[\n\tnull\n]".utf8)) + #expect(try item.data(for: .markdown) == Data("```json\n[\n\tnull\n]\n```\n".utf8)) +} + +@Test func exportStaticHTMLHasNoInteractiveOrExecutablePayload() throws { + let item = try JSONExportDocument.prepare(.string("&"), + options: .init(title: "&")) + let html = String(decoding: try item.data(for: .html), as: UTF8.self) + #expect(html.contains("class=\"light tabs-4\"")) + #expect(html.contains("</style><script>boom</script>&")) + #expect(html.contains("script-src 'none'") && html.contains("default-src 'none'")) + for forbidden in [" 1_000 { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { + try JSONExportDocument.prepare(.string(String(repeating: "x", count: 2_000)), options: .init(), meter: &meter) + } + #expect(meter.used(.workUnits) > 1_000 && meter.used(.retainedBytes) == 17) + #expect(throws: CancellationError.self) { try meter.checkpoint() } + } + try await task.value +} + +@Test func sharedExportPreparationsHaveIndependentConcurrentState() async throws { + let root = try JSONParser().parse(#"{"é":9007199254740993,"é":[-0.00e+2,null]}"#) + let options = JSONExportOptions(formats: [.json, .html, .plainText, .markdown, .rtf]) + let expected = try JSONExportDocument.prepareWithReport(root, options: options) + try await withThrowingTaskGroup(of: JSONExportReport.self) { group in + for _ in 0..<16 { group.addTask { try JSONExportDocument.prepareWithReport(root, options: options) } } + var count = 0 + for try await report in group { + #expect(report.operation == expected.operation && report.preparedBytes == expected.preparedBytes) + for format in report.result.availableFormats { #expect(try report.result.data(for: format) == expected.result.data(for: format)) } + count += 1 + } + #expect(count == 16) + } +} + +@Test func exportDeepWideAndRestrictiveConfigurationRemainBounded() throws { + var root: JSON = .string("LAST-CONTENT-SENTINEL") + for _ in 0..<64 { root = .array([root]) } + let wide = try JSONObject(members: (0..<300).map { JSONMember("k\($0)", value: .number(JSONNumber(integer: $0))) }) + for value in [root, .object(wide)] { + let item = try JSONExportDocument.prepare(value) + #expect(try JSONParser().parse(item.data(for: .json)) == value) + } + for indentation in [-1, 9] { #expect(throws: JSONExportFailure.invalidConfiguration) { try JSONExportDocument.prepare(.null, options: .init(indentation: indentation)) } } + for width in [0, 9] { #expect(throws: JSONExportFailure.invalidConfiguration) { try JSONExportDocument.prepare(.null, options: .init(tabWidth: width)) } } + #expect(throws: JSONExportFailure.invalidConfiguration) { try JSONExportDocument.prepare(.null, options: .init(formats: [])) } + #expect(try JSONExportDocument.prepare(.null, options: .init(formats: [.json, .rtf])).availableFormats == [.json, .rtf]) + #expect(throws: JSONExportFailure.preparationRequired(.pdf)) { try JSONExportDocument.prepare(.null, options: .init(formats: [.json, .pdf])) } +} + +#if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) +@Test func appleTransferTypesAndSelectedBytesMatchAvailableArtifacts() async throws { + #expect(JSON.exportedContentTypes() == [.json, .html, .utf8PlainText, .markdown]) + #expect(JSON.importedContentTypes() == [.json]) + #expect(JSONExportDocument.importedContentTypes().isEmpty) + let root: JSON = .array([.null]) + let item = try JSONExportDocument.prepare(root, options: .init(formats: [.json, .html, .plainText, .markdown, .rtf])) + for (format, type) in [(JSONExportFormat.json, UTType.json), (.plainText, .utf8PlainText), (.markdown, .markdown), (.html, .html)] { + let selected = try item.selecting([format]) + #expect(selected.exportedContentTypes() == [type]) + #expect(try await selected.exported(as: type) == item.data(for: format)) + #expect(try await root.exported(as: type) == item.data(for: format)) + } + let rtf = try item.selecting([.rtf]) + #expect(rtf.exportedContentTypes() == [.rtf]) + #expect(try await rtf.exported(as: .rtf) == item.data(for: .rtf)) + #expect(try await rtf.exported(as: .text) == item.data(for: .rtf)) + let markdown = try item.selecting([.markdown]) + do { _ = try await markdown.exported(as: .rtf); Issue.record("Hidden RTF bytes escaped selection") } + catch { #expect(!String(describing: error).isEmpty) } + // The guard rejects hidden candidates; generic text matching then reaches Markdown. + #expect(try await markdown.exported(as: .text) == item.data(for: .markdown)) + #expect(try await markdown.exported(as: .markdown) == item.data(for: .markdown)) + do { _ = try await markdown.exported(as: .json); Issue.record("Hidden JSON bytes escaped selected-format transfer") } + catch { #expect(!String(describing: error).isEmpty) } +} + +@Test @MainActor func exportItemProviderSelectionKeepsFilenameAndTypesCoherent() throws { + let item = try JSONExportDocument.prepare(.null, options: .init( + formats: [.json, .html, .rtf, .plainText, .markdown], filenameStem: "Selected")) + for (format, type) in [(JSONExportFormat.json, UTType.json), (.html, .html), + (.rtf, .rtf), (.plainText, .utf8PlainText), (.markdown, .markdown)] { + let selected = try item.selecting([format]) + let provider = NSItemProvider() + provider.register(selected) + #expect(provider.registeredTypeIdentifiers == [type.identifier]) + #expect(provider.suggestedName == "Selected." + format.filenameExtension) + } + let mixed = try item.selecting([.html, .markdown]) + let provider = NSItemProvider() + provider.register(mixed) + #expect(provider.registeredTypeIdentifiers == [UTType.html.identifier, UTType.markdown.identifier]) + #expect(provider.suggestedName == "Selected.html") +} + +@Test func appleNativeJSONImportIsStrictAndPreservesExactBytes() async throws { + let input = Data(#"{"é":9007199254740993,"é":-0.00e+2}"#.utf8) + let value = try await JSON(importing: input, contentType: .json) + #expect(try JSONWriter(options: .init(numberStyle: .preserved)).write(value) == input) + for bytes in [Data([0xff]), Data(#"{"a":1,"a":2}"#.utf8), Data("null true".utf8), Data("[1,]".utf8)] { + #expect(throws: JSONFailure.self) { try JSONParser().parse(bytes) } + do { _ = try await JSON(importing: bytes, contentType: .json); Issue.record("Invalid native JSON was accepted") } + catch { #expect(!String(describing: error).isEmpty) } // CoreTransferable wraps the parser error. + } +} +#endif + +@Test func exportResultCopyCancellationLeavesNoArtifactAndLatches() async throws { + let root: JSON = .string(String(repeating: "x", count: 2_000)) + let options = JSONExportOptions(formats: [.json]) + let baseline = try JSONExportDocument.prepareWithReport(root, options: options) + let threshold = baseline.operation[.workUnits] - 1 + let task = Task { + var meter = try JSONWorkMeter(limits: .balanced, domain: .writing) + meter.checkpointObserver = { work in if work >= threshold { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { try JSONExportDocument.prepare(root, options: options, meter: &meter) } + #expect(meter.used(.outputBytes) == baseline.operation[.outputBytes]) + #expect(meter.used(.retainedBytes) == 0) + #expect(throws: CancellationError.self) { try meter.checkpoint() } + } + try await task.value +} diff --git a/Tests/JSONHTMLTests/FormattedRendererTests.swift b/Tests/JSONHTMLTests/FormattedRendererTests.swift index 3beabc4..a8cfdea 100644 --- a/Tests/JSONHTMLTests/FormattedRendererTests.swift +++ b/Tests/JSONHTMLTests/FormattedRendererTests.swift @@ -98,6 +98,7 @@ private func body(_ html: String) throws -> [UInt8] { case .domNodes: report.statistics.domNodeUpperBound case .outlineEntries: report.statistics.outlineEntries case .metadataBytes: report.statistics.metadataBytes + case .annotations: report.statistics.annotations } for delta in [-1, 0, 1] { var limits = JSONHTMLLimits() @@ -108,6 +109,7 @@ private func body(_ html: String) throws -> [UInt8] { case .domNodes: limits.maxDOMNodes = cap case .outlineEntries: limits.maxOutlineEntries = cap case .metadataBytes: limits.maxMetadataBytes = cap + case .annotations: limits.maxAnnotations = cap } let renderer = JSONHTMLRenderer(options: .init(limits: limits)) if delta < 0 { diff --git a/Tests/JSONHTMLTests/InteractiveRendererTests.swift b/Tests/JSONHTMLTests/InteractiveRendererTests.swift index f4ae226..c3e4225 100644 --- a/Tests/JSONHTMLTests/InteractiveRendererTests.swift +++ b/Tests/JSONHTMLTests/InteractiveRendererTests.swift @@ -81,6 +81,7 @@ private func exact(_ root: JSON) throws -> [UInt8] { switch metric { case .outlineEntries: boundary = report.statistics.outlineEntries case .metadataBytes: boundary = report.statistics.metadataBytes + case .annotations: boundary = report.statistics.annotations case .documentBytes: boundary = report.statistics.documentBytes case .domNodes: boundary = report.statistics.domNodeUpperBound case .renderedJSONNodes: boundary = report.statistics.renderedJSONNodes @@ -90,6 +91,7 @@ private func exact(_ root: JSON) throws -> [UInt8] { switch metric { case .outlineEntries: limits.maxOutlineEntries = cap case .metadataBytes: limits.maxMetadataBytes = cap + case .annotations: limits.maxAnnotations = cap case .documentBytes: limits.maxDocumentBytes = cap case .domNodes: limits.maxDOMNodes = cap case .renderedJSONNodes: limits.maxRenderedJSONNodes = cap diff --git a/Tests/JSONHTMLTests/PDFExportTests.swift b/Tests/JSONHTMLTests/PDFExportTests.swift new file mode 100644 index 0000000..ceeccad --- /dev/null +++ b/Tests/JSONHTMLTests/PDFExportTests.swift @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import Testing +@testable import JSONHTML +#if canImport(AppKit) +import AppKit +#endif +#if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) +import CoreTransferable +import UniformTypeIdentifiers +#endif +#if canImport(PDFKit) && canImport(WebKit) +import PDFKit +#endif + +@Test func exportPDFConfigurationIsFiniteAndRejectsUnsupportedGeometry() throws { + for margin in [Double.nan, .infinity, -1, 145] { + #expect(throws: JSONPDFFailure.invalidConfiguration) { try JSONPDFOptions(marginPoints: margin).validate() } + } + #expect(throws: JSONPDFFailure.invalidConfiguration) { try JSONPDFOptions(timeoutMilliseconds: 600_001).validate() } + for limits in [JSONPDFLimits(maxHTMLBytes: .max), .init(maxCaptureBytes: .max), + .init(maxPDFBytes: .max), .init(maxPreparedBytes: .max), .init(maxPages: .max), + .init(maxContentHeightPoints: 14_401)] { + #expect(throws: JSONPDFFailure.invalidConfiguration) { try limits.validate() } + } + try JSONPDFOptions(marginPoints: 0, timeoutMilliseconds: 0, + limits: .init(maxHTMLBytes: 0, maxCaptureBytes: 0, maxPDFBytes: 0, + maxPreparedBytes: 0, maxPages: 0, maxContentHeightPoints: 0)).validate() +} + +@Test func exportPDFRequiresExplicitPreparationAndRetainsOriginalCapPolicy() throws { + #expect(throws: JSONExportFailure.preparationRequired(.pdf)) { + try JSONExportDocument.prepare(.null, options: .init(formats: [.html, .pdf])) + } + let item = try JSONExportDocument.prepare(.null) + #expect(item.availableFormats == [.json, .html, .plainText, .markdown]) + #expect(throws: JSONExportFailure.unavailableFormat(.pdf)) { try item.data(for: .pdf) } + #expect(throws: JSONExportFailure.unavailableFormat(.pdf)) { try item.selecting([.pdf]) } +} + +@Test func exportPDFCheckedCountersRejectOverflowAndUseInclusiveLimits() throws { + #expect(try pdfAdd(.pdfBytes, 2, 3, 5) == 5) + #expect(throws: JSONPDFFailure.limitExceeded(.pdfBytes, limit: 4, attempted: 5)) { try pdfAdd(.pdfBytes, 2, 3, 4) } + #expect(throws: JSONPDFFailure.counterOverflow(.preparedBytes)) { try pdfAdd(.preparedBytes, .max, 1, .max) } +} + +@Test @MainActor func exportPDFMissingHTMLNeverRerendersOrAltersSelection() async throws { + let original = try JSONExportDocument.prepare(.null, options: .init(formats: [.json])) + #expect(throws: JSONPDFFailure.missingHTML) { try original.pdfInput() } + do { _ = try await original.preparingPDF(); Issue.record("Missing HTML was silently regenerated") } + catch { #expect(error as? JSONPDFFailure == .missingHTML) } + #expect(original.availableFormats == [.json] && original.advertisedFormats == [.json]) +} + +#if canImport(PDFKit) && canImport(WebKit) +@MainActor private func pdfBootstrap() { + #if canImport(AppKit) + _ = NSApplication.shared + #endif +} +@MainActor private func pdfFixture(_ count: Int = 150) throws -> JSONExportDocument { + let values = (0..") } + return try JSONExportDocument.prepare(.array(values + [.string("LAST-CONTENT-SENTINEL")])) +} +@Suite(.serialized) struct ApplePDFTests { +@Test @MainActor func applePDFPaginatedA4AndLetterHaveCompleteSelectableRegions() async throws { + pdfBootstrap() + let original = try pdfFixture() + for paper in [JSONPDFPaper.a4, .usLetter] { + let report = try await original.preparingPDFWithReport(options: .init(paper: paper)) + let data = try report.result.data(for: .pdf) + let pdf = try #require(PDFDocument(data: data)) + #expect(report.pageCount == UInt64(pdf.pageCount) && report.pageCount >= 3) + let text = try #require(pdf.string) + for index in 0..<150 { #expect(text.components(separatedBy: "ROW-\(index)-END").count == 2) } + #expect(text.contains("9007199254740993") && text.contains("LAST-CONTENT-SENTINEL")) + #expect(pdf.page(at: pdf.pageCount - 1)?.string?.contains("LAST-CONTENT-SENTINEL") == true) + for index in 0.. 2_000) + #expect(pdf.string?.contains("LAST-CONTENT-SENTINEL") == true) + for source in ["null", "true", "9007199254740993", #""é😀{}\\""#, "[]", "{}"] { + let item = try JSONExportDocument.prepare(JSONParser().parse(source)) + let result = try await item.preparingPDF() + let document = try #require(PDFDocument(data: result.data(for: .pdf))) + #expect(document.pageCount == 1 && !(document.string ?? "").isEmpty) + } +} + +@Test @MainActor func applePDFWrapsWideContentAndHonorsZeroAndWideMargins() async throws { + pdfBootstrap() + let root: JSON = .string("FIRST-SENTINEL " + String(repeating: "https://example.test/long-key-😀-e\u{301}/", count: 300) + " LAST-CONTENT-SENTINEL") + let item = try JSONExportDocument.prepare(root) + for margin in [0.0, 144.0] { + let report = try await item.preparingPDFWithReport(options: .init(marginPoints: margin)) + let pdf = try #require(PDFDocument(data: report.result.data(for: .pdf))) + #expect(pdf.pageCount > 1) + #expect(pdf.page(at: 0)?.string?.contains("FIRST-SENTINEL") == true) + #expect(pdf.page(at: pdf.pageCount - 1)?.string?.contains("LAST-CONTENT-SENTINEL") == true) + } +} + +@Test @MainActor func applePDFNativeCapsRejectPartialArtifactsAndPreserveOriginal() async throws { + pdfBootstrap() + let item = try pdfFixture() + let originalJSON = try item.data(for: .json) + let small = try JSONExportDocument.prepare(.null) + let accepted = try await small.preparingPDFWithReport() + let inclusive = [JSONPDFLimits(maxHTMLBytes: accepted.inputHTMLBytes), + .init(maxCaptureBytes: accepted.capturedPDFBytes), .init(maxPDFBytes: accepted.outputPDFBytes), + .init(maxPreparedBytes: accepted.existingPreparedBytes + accepted.outputPDFBytes), + .init(maxPages: 1), .init(maxContentHeightPoints: 16)] + for limit in inclusive { #expect(try await small.preparingPDFWithReport(options: .init(limits: limit)).pageCount == 1) } + let cases = [ + JSONPDFLimits(maxHTMLBytes: 0), .init(maxCaptureBytes: 1), .init(maxPDFBytes: 1), + .init(maxPreparedBytes: 1), .init(maxPages: 1), .init(maxContentHeightPoints: 1) + ] + for limits in cases { + do { _ = try await item.preparingPDF(options: .init(limits: limits)); Issue.record("Restrictive PDF cap accepted output") } + catch { #expect(error is JSONPDFFailure || error is JSONFailure) } + #expect(try item.data(for: .json) == originalJSON && !item.availableFormats.contains(.pdf)) + } +} + +@Test @MainActor func applePDFCancellationAtEveryStageDoesNotInstallOutput() async throws { + pdfBootstrap() + let item = try pdfFixture(80) + for stage in [JSONPDFStage.input, .loading, .layout, .extraction, .capture, .composition, .installation] { + var task: Task? + var extractionVisits = 0 + task = Task { @MainActor in + try await item.preparingPDFWithReport(options: .init(), factory: { JSONPDFWebPage() }, + observer: { observed in + if observed == .extraction { extractionVisits += 1 } + if observed == stage && (stage != .extraction || extractionVisits >= 3) { task?.cancel() } + }) + } + do { _ = try await task!.value; Issue.record("Cancellation accepted PDF at \(stage)") } + catch { #expect(error is CancellationError) } + #expect(!item.availableFormats.contains(.pdf)) + } +} + +@Test @MainActor func applePDFOwnedBudgetsAreCumulativeAndInclusive() async throws { + pdfBootstrap() + let item = try JSONExportDocument.prepare(.string("é e\u{301} 😀 exact 9007199254740993")) + let baseline = try await item.preparingPDFWithReport() + #expect(baseline.operation[.workUnits] > baseline.inputHTMLBytes + baseline.outputPDFBytes) + let resultBytes = baseline.existingPreparedBytes + baseline.outputPDFBytes + #expect(baseline.operation[.retainedBytes] == resultBytes + UInt64(item.filenameStem.utf8.count) + 16 + UInt64(baseline.result.availableFormats.count) * 48) + for metric in [JSONResourceMetric.inputBytes, .workUnits, .outputBytes, .retainedBytes] { + var core = JSONLimits.balanced + let ceiling = metric == .retainedBytes ? baseline.operation.peakLogicalRetainedBytes : baseline.operation[metric] + core[metric] = ceiling + let exact = try await item.preparingPDFWithReport(options: .init(limits: .init(core: core))) + #expect(exact.pageCount == 1) + core[metric] = ceiling - 1 + do { _ = try await item.preparingPDF(options: .init(limits: .init(core: core))); Issue.record("Nested PDF helper reset \(metric)") } + catch { #expect((error as? JSONFailure)?.resource?.metric == metric) } + } +} + +@Test @MainActor func applePDFHiddenHTMLAndReplacementPreserveImmutablePolicy() async throws { + pdfBootstrap() + let original = try JSONExportDocument.prepare(.null).selecting([.json]) + let first = try await original.preparingPDFWithReport() + #expect(first.result.advertisedFormats == [.json, .pdf] && first.result.availableFormats.count == 5) + let second = try await first.result.preparingPDFWithReport() + #expect(second.result.availableFormats.count == 5 && second.existingPreparedBytes == first.existingPreparedBytes + first.outputPDFBytes) + #expect(try second.result.data(for: .json) == original.data(for: .json)) + let sum = try original.pdfInput().bytes + let tight = try JSONExportDocument.prepare(.null, options: .init(limits: .init(maxPreparedBytes: sum))) + do { _ = try await tight.preparingPDF(); Issue.record("Original aggregate cap was widened") } + catch { #expect((error as? JSONPDFFailure) != nil) } + #expect(!tight.availableFormats.contains(.pdf)) +} + +@Test @MainActor func applePDFFailureAndImmediateDeadlineNeverAdvertiseAnArtifact() async throws { + pdfBootstrap() + let item = try JSONExportDocument.prepare(.null) + do { _ = try await item.preparingPDF(options: .init(timeoutMilliseconds: 0)); Issue.record("Expired deadline accepted PDF") } + catch { #expect(error as? JSONPDFFailure == .timedOut) } + // Real iOS emoji fallback produced height 162 with nominal 16-point lines. + // Pagination uses CoreText visible ranges, not a WebKit line-grid assumption. + let nonGrid = SuspendedPDFBackend(ready: {}) + nonGrid.geometryHeight = 18.25 + let complete = try await item.preparingPDFWithReport(options: .init(), factory: { nonGrid }, observer: nil) + #expect(complete.pageCount == 1 && nonGrid.stopped) + let broken = SuspendedPDFBackend(ready: {}) + broken.failure = .captureFailed + do { _ = try await item.preparingPDFWithReport(options: .init(layout: .continuous), factory: { broken }, observer: nil); Issue.record("Failed capture accepted PDF") } + catch { #expect(error as? JSONPDFFailure == .captureFailed && broken.stopped) } + #expect(!item.availableFormats.contains(.pdf)) +} + +@Test @MainActor func applePDFTransferOffersOnlyPreparedSelectedBytes() async throws { + pdfBootstrap() + let item = try JSONExportDocument.prepare(.null) + let report = try await item.preparingPDFWithReport() + #if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) + let selected = try report.result.selecting([.pdf]) + #expect(selected.exportedContentTypes() == [.pdf]) + #expect(try await selected.exported(as: .pdf) == report.result.data(for: .pdf)) + let provider = NSItemProvider() + provider.register(selected) + #expect(provider.registeredTypeIdentifiers == [UTType.pdf.identifier]) + #expect(provider.suggestedName == selected.filenameStem + ".pdf") + do { _ = try await item.exported(as: .pdf); Issue.record("Unprepared transfer generated PDF") } catch {} + do { _ = try await report.result.selecting([.json]).exported(as: .pdf); Issue.record("Hidden PDF transferred") } catch {} + #endif +} + +@MainActor private final class SuspendedPDFBackend: JSONPDFPageBackend { + var pending: CheckedContinuation? + var stopped = false + var failure: JSONPDFFailure? + var geometryHeight: Double = 16 + var waitAt: JSONPDFStage = .capture + let ready: @MainActor () -> Void + init(ready: @escaping @MainActor () -> Void) { self.ready = ready } + func load(_ html: String) async throws { if waitAt == .loading { _ = try await suspend() } } + func layout(width: Double) async throws -> JSONPDFGeometry { + if waitAt == .layout { _ = try await suspend() } + return .init(width: width, height: geometryHeight, lineHeight: 16) + } + func textInfo() async throws -> JSONPDFTextInfo { .init(count: 1, tabWidth: 4) } + func textRun(at index: Int) async throws -> JSONPDFTextRun { .init(kind: "null", text: "null") } + func capture(_ rectangle: JSONPDFRectangle) async throws -> Data { + if let failure { throw failure } + return try await suspend() + } + private func suspend() async throws -> Data { + try await withCheckedThrowingContinuation { pending = $0; ready() } + } + func stop() { stopped = true } +} +@Test @MainActor func applePDFTimeoutReturnsWithoutWaitingForUncooperativeCapture() async throws { + pdfBootstrap() + let item = try JSONExportDocument.prepare(.null) + for stage in [JSONPDFStage.loading, .layout, .capture] { + let backend = SuspendedPDFBackend(ready: {}); backend.waitAt = stage + let start = ContinuousClock.now + do { + _ = try await item.preparingPDFWithReport(options: .init(layout: .continuous, timeoutMilliseconds: 100), + factory: { backend }, observer: nil) + Issue.record("Uncooperative capture did not time out") + } catch { #expect(error as? JSONPDFFailure == .timedOut) } + #expect(start.duration(to: .now) < .seconds(2) && backend.stopped) + // Resume the ignored late result so the qualification probe leaves no hung task. + backend.pending?.resume(returning: Data()); backend.pending = nil + await Task.yield() + #expect(!item.availableFormats.contains(.pdf)) + } +} + +@Test @MainActor func applePDFSimultaneousJobsHaveIndependentStateAndCancellation() async throws { + pdfBootstrap() + let item = try pdfFixture(80) + let stopped = SuspendedPDFBackend(ready: {}) + let cancelled = Task { @MainActor in + try await item.preparingPDFWithReport(options: .init(layout: .continuous), factory: { stopped }, observer: nil) + } + while stopped.pending == nil { await Task.yield() } + let successful = Task { @MainActor in try await item.preparingPDFWithReport() } + cancelled.cancel() + do { _ = try await cancelled.value; Issue.record("Cancelled job returned PDF") } + catch { #expect(error is CancellationError && stopped.stopped) } + stopped.pending?.resume(returning: Data()); stopped.pending = nil + let result = try await successful.value + #expect(result.pageCount > 1 && result.result.availableFormats.contains(.pdf)) +} +} +#else +@Test @MainActor func exportPDFUnavailableBackendRemainsUnadvertised() async throws { + let item = try JSONExportDocument.prepare(.null) + do { _ = try await item.preparingPDF(); Issue.record("Unsupported platform advertised PDF") } + catch { #expect(error as? JSONPDFFailure == .backendUnavailable) } + #expect(!item.availableFormats.contains(.pdf)) +} +#endif diff --git a/Tests/JSONHTMLTests/RTFExportTests.swift b/Tests/JSONHTMLTests/RTFExportTests.swift new file mode 100644 index 0000000..b6306e4 --- /dev/null +++ b/Tests/JSONHTMLTests/RTFExportTests.swift @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import Testing +@testable import JSONHTML +#if canImport(AppKit) +import AppKit +#elseif canImport(UIKit) +import UIKit +#endif + +@Test func exportRTFIsOptInAndEscapesExecutableLookingText() throws { + let value: JSON = .string("{}\\rtf1 \\object \\u123? ée\u{301}😀\t\n") + let item = try JSONExportDocument.prepare(value, options: .init(formats: [.rtf])) + let bytes = try item.data(for: .rtf) + let text = String(decoding: bytes, as: UTF8.self) + #expect(text.hasPrefix("{\\rtf1\\ansi") && text.hasSuffix("}")) + #expect(text.contains(#"\{\}"#)) + #expect(text.contains(#"\\\\rtf1"#) && text.contains(#"\\\\object"#)) + #expect(text.contains(#"\u233?e\u769?\u-10179?\u-8704?"#)) + #expect(text.contains(#"\\u0009\\u000a"#)) // JSON string escapes are not indentation controls. + #expect(!text.contains("\\field") && !text.contains("\\pict") && !text.contains("\\objdata")) + #expect(item.availableFormats == [.rtf] && item.advertisedFormats == [.rtf]) + #expect(try JSONExportDocument.prepare(.null).availableFormats.count == 4) + #expect(try JSONExportDocument.prepare(value, options: .init(formats: [.rtf], theme: .dark)).data(for: .rtf) == bytes) +} + +@Test func exportRTFStyleAndUTF16CapBoundariesAreInclusive() throws { + for delta in [-1, 0, 1] { + var limits = JSONExportLimits() + limits.maxRTFStyleRuns = UInt64(1 + delta); limits.maxRTFUTF16Units = 4 + if delta < 0 { + #expect(throws: JSONExportFailure.limitExceeded(.rtfStyleRuns, limit: 0, attempted: 1)) { + try JSONExportDocument.prepare(.null, options: .init(formats: [.rtf], limits: limits)) + } + } else { #expect(try JSONExportDocument.prepare(.null, options: .init(formats: [.rtf], limits: limits)).availableFormats == [.rtf]) } + limits.maxRTFStyleRuns = 1; limits.maxRTFUTF16Units = UInt64(4 + delta) + if delta < 0 { + #expect(throws: JSONExportFailure.limitExceeded(.rtfUTF16Units, limit: 3, attempted: 4)) { + try JSONExportDocument.prepare(.null, options: .init(formats: [.rtf], limits: limits)) + } + } else { #expect(try JSONExportDocument.prepare(.null, options: .init(formats: [.rtf], limits: limits)).availableFormats == [.rtf]) } + } + for cap in [UInt64(2), 3] { + let options = JSONExportOptions(formats: [.rtf], limits: .init(maxRTFStyleRuns: cap)) + if cap == 2 { + #expect(throws: JSONExportFailure.limitExceeded(.rtfStyleRuns, limit: 2, attempted: 3)) { + try JSONExportDocument.prepare(.array([.null]), options: options) + } + } else { #expect(try JSONExportDocument.prepare(.array([.null]), options: options).availableFormats == [.rtf]) } + } + // Two quotes plus a supplementary scalar require four UTF-16 units. + #expect(throws: JSONExportFailure.limitExceeded(.rtfUTF16Units, limit: 3, attempted: 4)) { + try JSONExportDocument.prepare(.string("😀"), options: .init(formats: [.rtf], limits: .init(maxRTFUTF16Units: 3))) + } + for limits in [JSONExportLimits(maxRTFStyleRuns: .max), .init(maxRTFUTF16Units: .max)] { + #expect(throws: JSONExportFailure.invalidConfiguration) { + try JSONExportDocument.prepare(.null, options: .init(formats: [.rtf], limits: limits)) + } + } + #expect(throws: JSONExportFailure.counterOverflow(.rtfUTF16Units)) { + try JSONExportBuffer.adding(.rtfUTF16Units, .max, 1, .max) + } +} + +@Test func exportRTFOutputRetentionAndAggregateCapsApplyBeforeGrowth() throws { + let root: JSON = .array([.string("é😀"), .null]) + let options = JSONExportOptions(formats: [.rtf]) + let baseline = try JSONExportDocument.prepareWithReport(root, options: options) + for metric in [JSONResourceMetric.workUnits, .outputBytes, .retainedBytes] { + let boundary = metric == .retainedBytes ? baseline.operation.peakLogicalRetainedBytes : baseline.operation[metric] + for delta in [-1, 0, 1] { + var changed = options + changed.limits.core[metric] = UInt64(Int64(boundary) + Int64(delta)) + if delta < 0 { #expect(throws: JSONFailure.self) { try JSONExportDocument.prepare(root, options: changed) } } + else { #expect(try JSONExportDocument.prepareWithReport(root, options: changed).operation == baseline.operation) } + } + } + for delta in [-1, 0, 1] { + var changed = options + changed.limits.maxRepresentationBytes = UInt64(Int64(baseline.preparedBytes) + Int64(delta)) + changed.limits.maxPreparedBytes = changed.limits.maxRepresentationBytes + if delta < 0 { #expect(throws: JSONExportFailure.self) { try JSONExportDocument.prepare(root, options: changed) } } + else { #expect(try JSONExportDocument.prepareWithReport(root, options: changed).preparedBytes == baseline.preparedBytes) } + } +} + +@Test func exportRTFSharesAdmissionAndTerminalFailureWithOtherFormats() throws { + let root: JSON = .array([.null, true]) + let alone = try JSONExportDocument.prepareWithReport(root, options: .init(formats: [.rtf])) + let combined = try JSONExportDocument.prepareWithReport(root, options: .init(formats: [.json, .rtf])) + #expect(combined.operation[.nodes] == 3 && alone.operation[.nodes] == 3) + var limits = JSONLimits.balanced; limits[.workUnits] = alone.operation[.workUnits] + #expect(throws: JSONFailure.self) { + try JSONExportDocument.prepare(root, options: .init(formats: [.json, .rtf], limits: .init(core: limits))) + } + var meter = try JSONWorkMeter(limits: .balanced, domain: .writing); try meter.retain(17) + #expect(throws: JSONExportFailure.self) { + try JSONExportDocument.prepare(root, options: .init(formats: [.json, .rtf], limits: .init(maxRTFStyleRuns: 0)), meter: &meter) + } + #expect(meter.used(.retainedBytes) == 17 && meter.used(.outputBytes) > 0) + let work = meter.used(.workUnits) + #expect(throws: JSONExportFailure.self) { try JSONExportDocument.prepare(root, options: .init(formats: [.rtf]), meter: &meter) } + #expect(meter.used(.workUnits) == work) +} + +@Test func exportRTFCancellationDuringEscapingIsTerminal() async throws { + let root: JSON = .string(String(repeating: "😀{}\\", count: 1_000)) + let options = JSONExportOptions(formats: [.rtf]) + let baseline = try JSONExportDocument.prepareWithReport(root, options: options) + let task = Task { + var meter = try JSONWorkMeter(limits: .balanced, domain: .writing); try meter.retain(17) + meter.checkpointObserver = { work in + if work >= baseline.operation[.workUnits] / 2 { withUnsafeCurrentTask { $0?.cancel() } } + } + #expect(throws: CancellationError.self) { try JSONExportDocument.prepare(root, options: options, meter: &meter) } + #expect(meter.used(.outputBytes) > 1_000 && meter.used(.outputBytes) < baseline.operation[.outputBytes]) + #expect(meter.used(.retainedBytes) == 17) + #expect(throws: CancellationError.self) { try meter.checkpoint() } + } + try await task.value +} + +#if canImport(AppKit) || canImport(UIKit) +@Test @MainActor func appleRTFNativeDecodePreservesTextAndActualStyles() throws { + let source = #"{"é":"é😀{}\\\t\n","n":-0.00e+2,"b":true,"z":null}"# + let root = try JSONParser().parse(source) + for width in [1, 4, 8] { + let item = try JSONExportDocument.prepare(root, options: .init(formats: [.plainText, .rtf], tabWidth: width)) + let decoded = try NSAttributedString(data: item.data(for: .rtf), options: [.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil) + #expect(try Data(decoded.string.utf8) == item.data(for: .plainText)) + #expect(decoded.length == decoded.string.utf16.count) + let body = decoded.string as NSString + for (text, rgb) in [("\"é\"", [33,75,110]), ("e\u{301}😀", [35,111,59]), ("-0.00e+2", [143,59,16]), ("true", [106,58,156]), ("null", [100,100,100])] { + let range = body.range(of: text) + try #require(range.location != NSNotFound && NSMaxRange(range) <= decoded.length) + let attributes = decoded.attributes(at: range.location, effectiveRange: nil) + #if canImport(AppKit) + let font = try #require(attributes[.font] as? NSFont) + let color = try #require((attributes[.foregroundColor] as? NSColor)?.usingColorSpace(.deviceRGB)) + let expected = try #require(NSColor(calibratedRed: CGFloat(rgb[0])/255, green: CGFloat(rgb[1])/255, blue: CGFloat(rgb[2])/255, alpha: 1).usingColorSpace(.deviceRGB)) + #expect(abs(color.redComponent * 255 - expected.redComponent * 255) < 0.01) + #expect(abs(color.greenComponent * 255 - expected.greenComponent * 255) < 0.01) + #expect(abs(color.blueComponent * 255 - expected.blueComponent * 255) < 0.01) + #else + let font = try #require(attributes[.font] as? UIFont) + let color = try #require(attributes[.foregroundColor] as? UIColor) + var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 + #expect(color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)) + #expect(abs(red * 255 - CGFloat(rgb[0])) < 0.01 && abs(green * 255 - CGFloat(rgb[1])) < 0.01 && abs(blue * 255 - CGFloat(rgb[2])) < 0.01) + #endif + #expect(font.fontName.contains("Menlo") && font.pointSize == 11) + } + let paragraph = try #require(decoded.attribute(.paragraphStyle, at: (decoded.string as NSString).range(of: "\"é\"").location, effectiveRange: nil) as? NSParagraphStyle) + #expect(paragraph.tabStops.count == 1) + #expect(abs(paragraph.tabStops[0].location - CGFloat(width) * 6.6) < 0.01) + } + var deep: JSON = .string("LAST-CONTENT-SENTINEL") + for _ in 0..<64 { deep = .array([deep]) } + let deepItem = try JSONExportDocument.prepare(deep, options: .init(formats: [.rtf, .plainText], indentation: 3)) + let deepDecoded = try NSAttributedString(data: deepItem.data(for: .rtf), options: [.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil) + #expect(try Data(deepDecoded.string.utf8) == deepItem.data(for: .plainText)) + let sentinel = (deepDecoded.string as NSString).range(of: "LAST-CONTENT-SENTINEL") + let deepParagraph = try #require(deepDecoded.attribute(.paragraphStyle, at: sentinel.location, effectiveRange: nil) as? NSParagraphStyle) + #expect(deepParagraph.tabStops.count == 192) + #expect(abs(deepParagraph.tabStops[191].location - 192 * 26.4) < 0.01) + for root in [JSON.null, true, .number(42), .string("\u{85}\u{2028}\u{2029}e\u{301}😀"), .array([]), .object(try JSONObject(members: []))] { + let item = try JSONExportDocument.prepare(root, options: .init(formats: [.rtf, .plainText])) + let decoded = try NSAttributedString(data: item.data(for: .rtf), options: [.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil) + #expect(try Data(decoded.string.utf8) == item.data(for: .plainText)) + } +} +#endif diff --git a/Tools/JSONHTMLBrowser/README.md b/Tools/JSONHTMLBrowser/README.md index 0670de3..37451a6 100644 --- a/Tools/JSONHTMLBrowser/README.md +++ b/Tools/JSONHTMLBrowser/README.md @@ -1,7 +1,7 @@ # Browser qualification -The public fixture generator imports JSON and JSONHTML only and writes eight -complete HTML documents, exact native JSON bytes and cumulative resource counts. +The public fixture generator imports JSON and JSONHTML only and writes ten +complete HTML documents (including mapped typed diagnostics), exact native JSON bytes and cumulative resource counts. The root package still has no external dependencies. Playwright 1.62.1 is a pinned qualification tool; install it in this directory and install its Chromium, Firefox and WebKit browsers. Alternatively set `JSONHTML_NODE` to an available @@ -9,7 +9,7 @@ Node executable and `PLAYWRIGHT_MODULE` to the installed pinned package. Run `Scripts/ci.py --lane browser` with the supported native toolchain selected. The lane verifies fixed asset hashes, generates release-native fixtures and runs -37 explicitly discovered browser cases. Missing browsers fail rather than becoming +46 explicitly discovered browser cases. Missing browsers fail rather than becoming skipped passes. Receipts include engine versions, fixture hashes, DOM observations, console/CSP/network errors, screenshots and a Chromium print PDF. diff --git a/Tools/JSONHTMLBrowser/Sources/BrowserFixtures/main.swift b/Tools/JSONHTMLBrowser/Sources/BrowserFixtures/main.swift index bbf7a69..c9a64ae 100644 --- a/Tools/JSONHTMLBrowser/Sources/BrowserFixtures/main.swift +++ b/Tools/JSONHTMLBrowser/Sources/BrowserFixtures/main.swift @@ -11,6 +11,15 @@ let hostile: JSON = .string("&\u{2028}\u{2029 var deep: JSON = .string("last-sentinel"); for _ in 0..<40 { deep = .array([deep]) } let wide: JSON = .array((0..<150).map { .string("literal [needle] \($0)") }) var zero = JSONHTMLRenderingOptions(presentation: .interactive); zero.limits.maxSearchQueryBytes = 0 +var annotations = JSONHTMLRenderingOptions(presentation: .interactive, initiallyExpandedDepth: 0) +annotations.annotations = [ + .init(path: [.key("é")], severity: .error, message: "\u{0}\r\n"), + .init(path: [.key("é")], severity: .warning, message: "Scalar-distinct target"), + .init(path: [.key("needle"), .index(99), .key("0")], severity: .error, message: "Missing target"), + .init(path: [.index(0)], severity: .information, message: "Numeric root mismatch"), + .init(path: [.key("~1/a\u{0}\r\n"), .index(0), .key("0")], severity: .error, message: "Exact control path")] +var formattedAnnotations = annotations; formattedAnnotations.presentation = .formatted +let document = try JSONParser().parseDocument(JSONWriter(options: .init(indentation: 2, numberStyle: .preserved)).write(exact), source: "private-label-not-emitted") let fixtures: [(String, JSON, JSONHTMLRenderingOptions)] = [ ("exact", exact, .init(title: "Exact offline JSON", presentation: .interactive)), ("hostile", hostile, .init(title: "", presentation: .interactive)), @@ -19,15 +28,20 @@ let fixtures: [(String, JSON, JSONHTMLRenderingOptions)] = [ ("null", .null, .init(presentation: .interactive)), ("zero-query", exact, zero), ("no-outline", exact, .init(presentation: .interactive, outlineDepth: nil)), - ("formatted", exact, .init())] + ("formatted", exact, .init()), + ("annotated", exact, annotations), + ("annotated-formatted", exact, formattedAnnotations)] var records: [JSON] = [] for (name, root, options) in fixtures { - let report = try JSONHTMLRenderer(options: options).renderWithReport(root) + let renderer = JSONHTMLRenderer(options: options) + let report = try name.hasPrefix("annotated") ? renderer.renderWithReport(document) : renderer.renderWithReport(root) try report.result.write(to: output.appendingPathComponent(name + ".html")) let bytes = try JSONWriter(options: .init(indentation: options.indentation, numberStyle: .preserved, indentationUnit: options.indentationUnit)).write(root) try bytes.write(to: output.appendingPathComponent(name + ".json")) records.append(try object([ JSONMember("name", value: .string(name)), + JSONMember("presentation", value: .string(options.presentation.rawValue)), + JSONMember("annotations", value: .number(JSONNumber(integer: report.statistics.annotations))), JSONMember("nodes", value: .number(JSONNumber(integer: report.statistics.renderedJSONNodes))), JSONMember("domUpperBound", value: .number(JSONNumber(integer: report.statistics.domNodeUpperBound))), JSONMember("documentBytes", value: .number(JSONNumber(integer: report.statistics.documentBytes))), @@ -36,4 +50,4 @@ for (name, root, options) in fixtures { JSONMember("logicalPeakBytes", value: .number(JSONNumber(integer: report.statistics.operation.peakLogicalRetainedBytes))) ])) } try JSONWriter(options: .init(indentation: 2)).write(.array(records)).write(to: output.appendingPathComponent("fixtures.json")) -print("JSONHTML_BROWSER_FIXTURES passed count=8") +print("JSONHTML_BROWSER_FIXTURES passed count=10") diff --git a/Tools/JSONHTMLBrowser/browser.cjs b/Tools/JSONHTMLBrowser/browser.cjs index 3a8fc4b..598a8e3 100644 --- a/Tools/JSONHTMLBrowser/browser.cjs +++ b/Tools/JSONHTMLBrowser/browser.cjs @@ -13,7 +13,7 @@ assert.equal(version, '1.62.1', 'Qualification requires the pinned Playwright ve const fixtures = path.resolve(process.argv[2]), out = path.resolve(process.argv[3]); fs.mkdirSync(out, {recursive: true}); const records = JSON.parse(fs.readFileSync(path.join(fixtures, 'fixtures.json'))); -assert.equal(records.length, 8); +assert.equal(records.length, 10); const sha = bytes => crypto.createHash('sha256').update(bytes).digest('hex'); const receipt = {status: 'running', playwright: version, fixtures: [], engines: [], cases: [], limitations: ['Screenshots are captured in Chromium; WebKit screenshots would inject a CSP-blocked synchronization stylesheet. WebKit DOM/layout/interaction checks still enforce CSP.', 'Playwright WebKit is not native Safari qualification.', 'Viewport tests do not qualify physical mobile browsers or VoiceOver.', 'Clipboard API acceptance is separate from platform clipboard transformations; exact downloads are independently compared.', 'Search chunking is not an instruction, CPU, heap or process RSS guarantee.']}; for (const name of fs.readdirSync(fixtures).sort()) { @@ -43,7 +43,7 @@ async function main() { } try { browser = await playwright[engine].launch(launch); } catch (error) { - receipt.engines.push({name: engine, status: 'blocked', error: error.message, executedCases: 0, unexecutedCases: engine === 'chromium' ? 13 : 12}); + receipt.engines.push({name: engine, status: 'blocked', error: error.message, executedCases: 0, unexecutedCases: engine === 'chromium' ? 16 : 15}); save(); continue; } activeBrowsers.push(browser); @@ -85,7 +85,7 @@ async function main() { assert.ok(dom <= record.domUpperBound, 'DOM bound undercounts actual nodes'); entry.browserPolicyEvents = browserPolicyEvents; entry.actualDOMNodes = dom; entry.domUpperBound = record.domUpperBound; - if (fixture !== 'formatted') { + if (record.presentation === 'interactive') { assert.equal(await page.locator('#jh-tree .node').count(), record.nodes); assert.equal(await page.locator('#jh-toolbar').isVisible(), javaScriptEnabled); assert.ok(await page.locator('.pointer-actions').count() > 0); @@ -221,6 +221,40 @@ async function main() { await page.evaluate(() => {const q = document.getElementById('jh-query'); q.value = '\ud800'; q.dispatchEvent(new Event('input'));}); await status(page, 'Unicode scalar text'); }); + for (const protocol of ['http', 'file']) await run('typed-annotations-' + protocol, 'annotated', protocol, true, async page => { + assert.equal(await page.locator('[id^="jh-a-"]').count(), 5); + assert.equal(await page.locator('img').count(), 0); + assert.equal(await page.locator('script').count(), 1); + assert.equal(await page.locator('#jh-a-0 a').getAttribute('data-target'), 'jh-n-4'); + assert.equal(await page.locator('#jh-a-1 a').getAttribute('data-target'), 'jh-n-5'); + assert.equal(await page.locator('#jh-a-2').getAttribute('data-missing'), 'true'); + assert.equal(await page.locator('#jh-a-2 a').getAttribute('data-target'), 'jh-n-6'); + assert.ok((await page.locator('#jh-a-2').textContent()).includes('Parent source span:')); + assert.ok((await page.locator('#jh-a-2').textContent()).includes('index [99] / key "0"')); + assert.ok((await page.locator('#jh-a-0').textContent()).includes('\\u0000\\u000d\\u000a')); + assert.equal(await page.locator('#jh-a-4 a').getAttribute('data-target'), 'jh-n-3'); + const expected = await page.locator('#jh-a-4').textContent(); + assert.ok(expected.includes('~1/a\\u0000\\u000d\\u000a')); + await page.locator('#jh-a-4 a').click(); + assert.equal(await page.evaluate(() => document.activeElement.id), 'jh-n-3'); + assert.equal(await page.locator('#jh-n-0').getAttribute('open') !== null, true); + assert.equal(await page.locator('#jh-n-1').getAttribute('open') !== null, true); + assert.equal(await page.locator('#jh-n-3').getAttribute('aria-describedby'), 'jh-a-4'); + const download = await downloaded(page, '[data-action="download-json"]'); + assert.deepEqual(download.bytes, fs.readFileSync(path.join(fixtures, 'annotated.json'))); + assert.equal(await page.evaluate(() => document.body.textContent.includes('private-label-not-emitted')), false); + }); + await run('formatted-annotations-no-script', 'annotated-formatted', 'file', false, async page => { + assert.equal(await page.locator('script').count(), 0); + assert.equal(await page.locator('pre code').textContent(), fs.readFileSync(path.join(fixtures, 'annotated-formatted.json'), 'utf8')); + assert.equal(await page.locator('#jh-n-4').getAttribute('aria-describedby'), 'jh-a-0'); + assert.equal(await page.locator('#jh-a-3').getAttribute('data-missing'), 'true'); + assert.equal(await page.locator('#jh-a-3 a').getAttribute('href'), '#jh-n-0'); + await page.locator('#jh-a-4 a').click(); + assert.ok(page.url().endsWith('#jh-n-3')); + await page.emulateMedia({media: 'print'}); + assert.ok(await page.locator('#jh-a-4').isVisible()); + }); if (engine === 'chromium') await run('native-browser-clipboard', 'exact', 'http', true, async (page, context) => { await context.grantPermissions(['clipboard-read', 'clipboard-write']); // Keep previous clipboard text in browser memory only; never put it in receipts. @@ -239,12 +273,12 @@ async function main() { }); await browser.close(); } - receipt.expectedCases = 37; receipt.passedCases = receipt.cases.filter(c => c.status === 'passed').length; + receipt.expectedCases = 46; receipt.passedCases = receipt.cases.filter(c => c.status === 'passed').length; receipt.unexecutedCases = receipt.engines.reduce((sum, e) => sum + (e.unexecutedCases || 0), 0); if (receipt.unexecutedCases) {receipt.status = 'blocked'; save(); console.error('Browser startup blocked; unexecuted cases=' + receipt.unexecutedCases); process.exitCode = 2; return;} - assert.equal(receipt.cases.length, 37); - assert.equal(receipt.cases.filter(c => c.status === 'passed').length, 37); - receipt.status = 'passed'; receipt.expectedCases = 37; receipt.passedCases = 37; receipt.unexpectedSkips = 0; - save(); console.log('JSONHTML_BROWSER passed engines=3 cases=37 skips=0'); + assert.equal(receipt.cases.length, 46); + assert.equal(receipt.cases.filter(c => c.status === 'passed').length, 46); + receipt.status = 'passed'; receipt.expectedCases = 46; receipt.passedCases = 46; receipt.unexpectedSkips = 0; + save(); console.log('JSONHTML_BROWSER passed engines=3 cases=46 skips=0'); } main().catch(error => {receipt.status = 'failed'; receipt.error = error.stack; save(); console.error(error); process.exitCode = 1;}).finally(async () => {await Promise.all(activeBrowsers.map(b => b.close().catch(() => {}))); for (const data of isolatedDataDirectories) fs.rmSync(data, {recursive: true, force: true}); server.close();}); diff --git a/Tools/JSONHTMLExport/Package.swift b/Tools/JSONHTMLExport/Package.swift new file mode 100644 index 0000000..18f0c10 --- /dev/null +++ b/Tools/JSONHTMLExport/Package.swift @@ -0,0 +1,10 @@ +// swift-tools-version: 6.4 +// SPDX-License-Identifier: Apache-2.0 +import PackageDescription +let package = Package(name: "JSONHTMLExport", platforms: [.macOS("27.0"), .iOS("27.0")], + products: [.executable(name: "ExportConsumer", targets: ["ExportConsumer"])], + dependencies: [.package(name: "Swift-JSON", path: "../..")], + targets: [.executableTarget(name: "ExportConsumer", dependencies: + ["JSON", "JSONSchema", "JSONStreaming", "JSONPatch", "JSONHTML"].map { + .product(name: $0, package: "Swift-JSON") + }, resources: [.copy("Fixtures")])], swiftLanguageModes: [.v6]) diff --git a/Tools/JSONHTMLExport/Probes/WebPageBackend.swift b/Tools/JSONHTMLExport/Probes/WebPageBackend.swift new file mode 100644 index 0000000..eca3ca9 --- /dev/null +++ b/Tools/JSONHTMLExport/Probes/WebPageBackend.swift @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// T0 experiment only: compiling/capturing a region does not qualify paginated PDF. +import Foundation +import WebKit +#if os(macOS) +import AppKit +import PDFKit +#endif + +struct OfflineNavigation: WebPage.NavigationDeciding { + @MainActor mutating func decidePolicy(for action: WebPage.NavigationAction, + preferences: inout WebPage.NavigationPreferences) async -> WKNavigationActionPolicy { + preferences.allowsContentJavaScript = false + return action.request.url?.absoluteString == "about:blank" ? .allow : .cancel + } +} + +@main struct BackendProbe { + @MainActor static func main() async throws { + #if os(macOS) + _ = NSApplication.shared + #endif + var configuration = WebPage.Configuration() + configuration.websiteDataStore = .nonPersistent() + configuration.loadsSubresources = false + configuration.defaultNavigationPreferences.allowsContentJavaScript = false + let page = WebPage(configuration: configuration, navigationDecider: OfflineNavigation()) + defer { page.stopLoading() } + page.mediaType = .print + let rows = (0..<150).map { "row-\($0): 9007199254740993 é é 😀" }.joined(separator: "\n") + let html = "
\(rows)\nLAST-CONTENT-SENTINEL
" + var finished = false + for try await event in page.load(html: html) { + if event == .finished { finished = true } + } + guard finished else { throw CocoaError(.fileReadUnknown) } + let data = try await page.exported(as: .pdf(region: .rect(CGRect(x: 0, y: 0, width: 595.28, height: 3_500)))) + guard data.count <= 8_388_608 else { throw CocoaError(.fileWriteOutOfSpace) } + #if os(macOS) + let output = URL(fileURLWithPath: CommandLine.arguments[1]) + try data.write(to: output) + guard let document = PDFDocument(data: data) else { throw CocoaError(.fileReadCorruptFile) } + let bounds = (0..","quote":"\" & < >","rtf":"{\\rtf1\\ansi injected}","fences":"```````````` ~~~~~~~~~~~~","unicode":"é é 😀 漢字 العربية","escape":"\\u0000 \\t \\n","whitespace":" embedded spaces\tand tab\nand newline"} diff --git a/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/mixed-roots.json b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/mixed-roots.json new file mode 100644 index 0000000..55dcf92 --- /dev/null +++ b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/mixed-roots.json @@ -0,0 +1 @@ +[{},[],null,true,false,"quoted scalar",{"items":[1,2,3],"tail":"LAST-CONTENT-SENTINEL"}] diff --git a/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/registry.json b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/registry.json new file mode 100644 index 0000000..ad6ef10 --- /dev/null +++ b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/registry.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "fixtures": [ + { + "path": "fixtures/exact-numbers.json", + "sha256": "fa9946fc911c790ba089dfaeb741675462a41e510fe02098d286d95ad2c03e55", + "expected_native_parse": "accepted", + "purpose": "Lexeme preservation beyond common Foundation numeric ranges." + }, + { + "path": "fixtures/scalar-keys.json", + "sha256": "527da46df8672d01f5d9dff53fcd5e2c1af952dc0bcbd2f3deb81d050509bb37", + "expected_native_parse": "accepted", + "purpose": "Exact scalar-distinct identity, controls, pointer and special keys." + }, + { + "path": "fixtures/inert-payloads.json", + "sha256": "1c3ccb711c0dc9eff04dd2ac5ed56a6b175e5b1d5cce68bb97bebc1d511e53a9", + "expected_native_parse": "accepted", + "purpose": "HTML/CSP/Markdown/RTF injection resistance and Unicode fidelity." + }, + { + "path": "fixtures/mixed-roots.json", + "sha256": "51db3eeed571a17ea6e0bff66a2c84a521f73e1066e90c7e973d4e4c101be785", + "expected_native_parse": "accepted", + "purpose": "Mixed values and a last-content sentinel." + }, + { + "path": "fixtures/duplicate-key.json", + "sha256": "aa69fb36553abd051ee9e72200f4a6d92fc9db3e99bb95548d8b67aec744e6ad", + "expected_native_parse": "rejected", + "purpose": "Strict duplicate-key rejection after decoded key identity." + }, + { + "path": "fixtures/trailing-comma.json", + "sha256": "bbdc773a1abf3ad27738d5cdfb9d662b1372540a50bb607fe721c29c1bed434f", + "expected_native_parse": "rejected", + "purpose": "Strict grammar rejection." + } + ], + "native_execution_status": "not_run" +} diff --git a/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/scalar-keys.json b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/scalar-keys.json new file mode 100644 index 0000000..461799d --- /dev/null +++ b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/scalar-keys.json @@ -0,0 +1 @@ +{"\u00e9":"composed","e\u0301":"decomposed","":"empty","0":[null,true,false],"a/b~c":"pointer","\u0000\r\n":"controls","__proto__":"ordinary","constructor":"ordinary"} diff --git a/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/trailing-comma.json b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/trailing-comma.json new file mode 100644 index 0000000..5701181 --- /dev/null +++ b/Tools/JSONHTMLExport/Sources/ExportConsumer/Fixtures/trailing-comma.json @@ -0,0 +1 @@ +{"x":1,} diff --git a/Tools/JSONHTMLExport/Sources/ExportConsumer/main.swift b/Tools/JSONHTMLExport/Sources/ExportConsumer/main.swift new file mode 100644 index 0000000..1dacbc1 --- /dev/null +++ b/Tools/JSONHTMLExport/Sources/ExportConsumer/main.swift @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONHTML +import JSONSchema +import JSONStreaming +import JSONPatch +#if canImport(AppKit) +import AppKit +import PDFKit +#elseif canImport(UIKit) +import UIKit +import PDFKit +#endif +#if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) +import CoreTransferable +import UniformTypeIdentifiers +#endif + +struct FixtureRegistry: Decodable { + struct Fixture: Decodable { let path: String; let sha256: String; let expected_native_parse: String } + let fixtures: [Fixture] +} +struct Observation: Codable { let name: String; let passed: Bool } +struct Receipt: Codable { + let status: String + let cases: [Observation] + let samples: [String: Int] + let timingsMilliseconds: [Double] + let preparedBytes: UInt64 + let workUnits: UInt64 + let peakLogicalRetainedBytes: UInt64 + let rtfPreparedBytes: UInt64 + let rtfWorkUnits: UInt64 + let rtfPeakLogicalRetainedBytes: UInt64 + let pdfReports: [PDFObservation] + let limitations: [String] +} + +struct PDFObservation: Codable { + let layout: String + let paper: String + let pages: UInt64 + let bytes: UInt64 + let capturedBytes: UInt64 + let work: UInt64 + let peakLogicalBytes: UInt64 + let elapsedMilliseconds: Double + let backend: String +} + +@main struct ExportConsumer { + @MainActor static func main() async throws { + let output = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true) + try FileManager.default.createDirectory(at: output, withIntermediateDirectories: true) + var cases: [Observation] = [] + func check(_ name: String, _ passed: Bool) throws { + cases.append(.init(name: name, passed: passed)) + if !passed { throw CocoaError(.fileReadCorruptFile) } + } + let fixtureRoot = Bundle.module.resourceURL!.appendingPathComponent("Fixtures") + let registry = try JSONDecoder().decode(FixtureRegistry.self, from: Data(contentsOf: fixtureRoot.appendingPathComponent("registry.json"))) + for fixture in registry.fixtures { + let bytes = try Data(contentsOf: fixtureRoot.appendingPathComponent(URL(fileURLWithPath: fixture.path).lastPathComponent)) + if fixture.expected_native_parse == "accepted" { + let parsed = try JSONParser().parse(bytes) + let prepared = try JSONExportDocument.prepare(parsed) + try check("fixture-" + fixture.path, try JSONParser().parse(prepared.data(for: .json)) == parsed) + } else { + var rejected = false + do { _ = try JSONParser().parse(bytes) } catch is JSONFailure { rejected = true } + try check("fixture-" + fixture.path, rejected) + } + } + let source = #"{"é":9007199254740993,"é":-0.00e+2,"empty":[],"end":"LAST-CONTENT-SENTINEL😀"}"# + let value = try JSONParser().parse(source) + let report = try JSONExportDocument.prepareWithReport(value, options: .init(filenameStem: "Exact-JSON")) + let item = report.result + try check("available-four", item.availableFormats == [.json, .plainText, .markdown, .html]) + try check("advertised-four", item.advertisedFormats == item.availableFormats) + try check("one-root-admission", report.operation[.nodes] == 5) + try check("aggregate-artifact-sum", report.preparedBytes == report.representationBytes.values.reduce(0, +)) + try check("exact-native-roundtrip", try JSONWriter(options: .init(numberStyle: .preserved)).write(JSONParser().parse(item.data(for: .json))) == Data(source.utf8)) + try check("pretty-plain-body", try item.data(for: .json) == item.data(for: .plainText)) + var samples: [String: Int] = [:] + for format in [JSONExportFormat.json, .plainText, .markdown, .html] { + let bytes = try item.data(for: format) + let name = "Exact-JSON." + format.filenameExtension + try bytes.write(to: output.appendingPathComponent(name)); samples[name] = bytes.count + let selected = try item.selecting([format]) + try check("selection-" + format.rawValue, selected.advertisedFormats == [format] && selected.availableFormats == item.availableFormats) + } + #if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) + try check("direct-native-import-only", JSON.importedContentTypes() == [.json]) + try check("prepared-export-only", JSONExportDocument.importedContentTypes().isEmpty) + for (format, type) in [(JSONExportFormat.json, UTType.json), (.html, .html), (.plainText, .utf8PlainText), (.markdown, .markdown)] { + let selected = try item.selecting([format]) + try check("instance-discovery-" + format.rawValue, selected.exportedContentTypes() == [type]) + try check("transfer-bytes-" + format.rawValue, try await selected.exported(as: type) == item.data(for: format)) + } + let fileDirectory = output.appendingPathComponent("SystemExport", isDirectory: true) + try FileManager.default.createDirectory(at: fileDirectory, withIntermediateDirectories: true) + let path = try await item.selecting([.json]).export(to: fileDirectory, contentType: .json) + try check("system-filename-and-file", path.lastPathComponent == "Exact-JSON.json" && Data(contentsOf: path) == item.data(for: .json)) + let imported = try await JSON(importing: path, contentType: .json) + try check("system-file-strict-import", try JSONWriter(options: .init(numberStyle: .preserved)).write(imported) == Data(source.utf8)) + #endif + let rich = try JSONExportDocument.prepareWithReport(value, options: .init(formats: [.json, .html, .rtf, .plainText, .markdown], filenameStem: "Exact-JSON")) + try check("rtf-explicit-five", rich.result.availableFormats == [.json, .html, .rtf, .plainText, .markdown]) + try check("rtf-one-admission", rich.operation[.nodes] == 5) + try check("rtf-cumulative-work", rich.operation[.workUnits] > report.operation[.workUnits]) + try check("rtf-aggregate", rich.preparedBytes == rich.representationBytes.values.reduce(0, +)) + let rtfBytes = try rich.result.data(for: .rtf) + try rtfBytes.write(to: output.appendingPathComponent("Exact-JSON.rtf")); samples["Exact-JSON.rtf"] = rtfBytes.count + #if canImport(AppKit) || canImport(UIKit) + let decoded = try NSAttributedString(data: rtfBytes, options: [.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil) + try check("rtf-native-text", try Data(decoded.string.utf8) == rich.result.data(for: .plainText) && decoded.length == decoded.string.utf16.count) + let key = (decoded.string as NSString).range(of: "\"é\"") + let number = (decoded.string as NSString).range(of: "9007199254740993") + #if canImport(AppKit) + let font = decoded.attribute(.font, at: key.location, effectiveRange: nil) as? NSFont + #else + let font = decoded.attribute(.font, at: key.location, effectiveRange: nil) as? UIFont + #endif + let keyColor = decoded.attribute(.foregroundColor, at: key.location, effectiveRange: nil) as? NSObject + let numberColor = decoded.attribute(.foregroundColor, at: number.location, effectiveRange: nil) as? NSObject + try check("rtf-native-styles", font?.fontName.contains("Menlo") == true && font?.pointSize == 11 && keyColor != nil && numberColor != nil && keyColor != numberColor) + #endif + let richOnly = try rich.result.selecting([.rtf]) + try check("rtf-selection", richOnly.advertisedFormats == [.rtf] && richOnly.availableFormats.count == 5) + #if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) + var providerNamesMatch = true + for (format, type) in [(JSONExportFormat.json, UTType.json), (.html, .html), + (.rtf, .rtf), (.plainText, .utf8PlainText), (.markdown, .markdown)] { + let selected = try rich.result.selecting([format]) + let provider = NSItemProvider() + provider.register(selected) + providerNamesMatch = providerNamesMatch && provider.registeredTypeIdentifiers == [type.identifier] + && provider.suggestedName == "Exact-JSON." + format.filenameExtension + } + try check("provider-selected-types-and-filenames", providerNamesMatch) + #endif + + #if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) + try check("rtf-content-type", richOnly.exportedContentTypes() == [.rtf]) + try check("rtf-transfer-bytes", try await richOnly.exported(as: .rtf) == rtfBytes) + let richPath = try await richOnly.export(to: fileDirectory, contentType: .rtf) + try check("rtf-system-filename", richPath.lastPathComponent == "Exact-JSON.rtf" && Data(contentsOf: richPath) == rtfBytes) + #endif + var pdfReports: [PDFObservation] = [] + #if canImport(WebKit) && canImport(PDFKit) + #if canImport(AppKit) + _ = NSApplication.shared + #endif + let pdfReport = try await rich.result.preparingPDFWithReport() + try check("pdf-explicit-six", pdfReport.result.availableFormats.count == 6 && !rich.result.availableFormats.contains(.pdf)) + try check("pdf-native-unchanged", try pdfReport.result.data(for: .json) == rich.result.data(for: .json)) + try check("pdf-no-root-readmission", pdfReport.operation[.nodes] == 0) + try check("pdf-input-html", try pdfReport.inputHTMLBytes == UInt64(rich.result.data(for: .html).count)) + try check("pdf-cumulative-output", pdfReport.operation[.outputBytes] == pdfReport.capturedPDFBytes + pdfReport.outputPDFBytes) + let pdfOnly = try pdfReport.result.selecting([.pdf]) + #if canImport(CoreTransferable) && canImport(UniformTypeIdentifiers) + try check("pdf-content-type", pdfOnly.exportedContentTypes() == [.pdf]) + let pdfProvider = NSItemProvider() + pdfProvider.register(pdfOnly) + try check("pdf-provider-type-and-filename", pdfProvider.registeredTypeIdentifiers == [UTType.pdf.identifier] && pdfProvider.suggestedName == "Exact-JSON.pdf") + try check("pdf-transfer-bytes", try await pdfOnly.exported(as: .pdf) == pdfReport.result.data(for: .pdf)) + let pdfPath = try await pdfOnly.export(to: fileDirectory, contentType: .pdf) + try check("pdf-system-filename", try pdfPath.lastPathComponent == "Exact-JSON.pdf" && Data(contentsOf: pdfPath) == pdfReport.result.data(for: .pdf)) + #endif + try check("pdf-original-unavailable", !rich.result.advertisedFormats.contains(.pdf)) + let rows = (0..<150).map { JSON.string("ROW-\($0)-END 9007199254740993 é e\u{301} 😀 العربية https://example.test/wide/long/value") } + let pageItem = try JSONExportDocument.prepare(.array(rows + [.string("LAST-CONTENT-SENTINEL")]), options: .init(formats: [.html])) + for options in [JSONPDFOptions(), .init(paper: .usLetter), .init(layout: .continuous)] { + let rendered = try await pageItem.preparingPDFWithReport(options: options) + let bytes = try rendered.result.data(for: .pdf) + let pdf = try PDFDocument(data: bytes).unwrap() + let name = options.layout == .continuous ? "Continuous.pdf" : options.paper == .a4 ? "A4.pdf" : "Letter.pdf" + try bytes.write(to: output.appendingPathComponent(name)); samples[name] = bytes.count + if options.layout == .paginated { + let width = options.paper == .a4 ? 595.275590551 : 612.0 + let height = options.paper == .a4 ? 841.889763780 : 792.0 + try check("pdf-geometry-" + name, pdf.pageCount >= 3 && (0.. 2_000) + } + try check("pdf-last-page-" + name, pdf.page(at: pdf.pageCount - 1)?.string?.contains("LAST-CONTENT-SENTINEL") == true) + let elapsed = rendered.elapsed.components + pdfReports.append(.init(layout: options.layout.rawValue, paper: options.paper.rawValue, + pages: rendered.pageCount, bytes: rendered.outputPDFBytes, capturedBytes: rendered.capturedPDFBytes, + work: rendered.operation[.workUnits], peakLogicalBytes: rendered.operation.peakLogicalRetainedBytes, + elapsedMilliseconds: Double(elapsed.seconds) * 1_000 + Double(elapsed.attoseconds) / 1e15, backend: rendered.backend)) + } + let cancelled = Task { @MainActor in try await rich.result.preparingPDF() } + cancelled.cancel() + var cancellationRejected = false + do { _ = try await cancelled.value } catch is CancellationError { cancellationRejected = true } + try check("pdf-pre-cancellation", cancellationRejected && !rich.result.availableFormats.contains(.pdf)) + #endif + var times: [Double] = [] + let clock = ContinuousClock() + for _ in 0..<7 { + let start = clock.now + let measured = try JSONExportDocument.prepareWithReport(value) + try check("measurement-complete-\(times.count)", measured.result.availableFormats.count == 4) + let elapsed = start.duration(to: clock.now).components + times.append(Double(elapsed.seconds) * 1_000 + Double(elapsed.attoseconds) / 1e15) + } + let receipt = Receipt(status: "passed", cases: cases, samples: samples, timingsMilliseconds: times, + preparedBytes: report.preparedBytes, workUnits: report.operation[.workUnits], + peakLogicalRetainedBytes: report.operation.peakLogicalRetainedBytes, + rtfPreparedBytes: rich.preparedBytes, rtfWorkUnits: rich.operation[.workUnits], + rtfPeakLogicalRetainedBytes: rich.operation.peakLogicalRetainedBytes, + pdfReports: pdfReports, limitations: ["API/file transport consumer, not share-sheet, drag/drop or recipient UI qualification.", + "PDF native generation/text/geometry is independent of receiving-app UI and physical-device integration.", "Logical retention is not RSS; tiny sample timing does not prove throughput ceilings."]) + let encoder = JSONEncoder(); encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + try encoder.encode(receipt).write(to: output.appendingPathComponent("consumer.json")) + print("JSONHTML_EXPORT_CONSUMER passed cases=\(cases.count) artifacts=\(samples.count) measurements=\(times.count)") + } +} + +private extension Optional { + func unwrap() throws -> Wrapped { guard let value = self else { throw CocoaError(.fileReadCorruptFile) }; return value } +} diff --git a/Tools/WasmAudit/Package.swift b/Tools/WasmAudit/Package.swift index eaaa2f4..b222a1a 100644 --- a/Tools/WasmAudit/Package.swift +++ b/Tools/WasmAudit/Package.swift @@ -10,7 +10,8 @@ let package = Package( .product(name: "JSON", package: "Swift-JSON"), .product(name: "JSONSchema", package: "Swift-JSON"), .product(name: "JSONStreaming", package: "Swift-JSON"), - .product(name: "JSONPatch", package: "Swift-JSON")]), + .product(name: "JSONPatch", package: "Swift-JSON"), + .product(name: "JSONHTML", package: "Swift-JSON")]), .executableTarget(name: "WasmAudit", dependencies: ["WasmAuditKit"]), .testTarget(name: "WasmAuditTests", dependencies: ["WasmAuditKit"]) ], swiftLanguageModes: [.v6]) diff --git a/Tools/WasmAudit/README.md b/Tools/WasmAudit/README.md index 14f3886..2bf46f0 100644 --- a/Tools/WasmAudit/README.md +++ b/Tools/WasmAudit/README.md @@ -35,3 +35,9 @@ for qualification, and `command -- ...` passes explicit executable arguments. See the [WASI contract](../../Documentation/Architecture/WebAssembly.md) for cooperative cancellation, safety/measurement policy and unqualified host modes. + +The integrated JSONHTML consumer adds the fifth public product and a fourteenth +Swift Testing function covering six exact HTML/export/budget/cancellation/backend +checks. Node runs all six root test bundles (including 49 portable HTML functions); +Node and WasmKit run the 14-function public consumer. Apple transfer and PDF are +not portable backend claims. See [receiving qualification](../../Documentation/Architecture/Release1.3/ReceivingQualification.md). diff --git a/Tools/WasmAudit/Sources/WasmAudit/main.swift b/Tools/WasmAudit/Sources/WasmAudit/main.swift index 8405764..1768ddd 100644 --- a/Tools/WasmAudit/Sources/WasmAudit/main.swift +++ b/Tools/WasmAudit/Sources/WasmAudit/main.swift @@ -23,6 +23,7 @@ import JSONSchema try Probes.codingAndScope(); print("CASE coding_and_scope passed") try Probes.cumulativeBudgets(); print("CASE cumulative_budgets passed") try Probes.patches(); print("CASE patch_and_merge_patch passed") + case "html": try await Probes.htmlRenderingAndExports() case "resources": try Probes.resourceRegistry(); print("CASE nine_pinned_resources passed") case "schema": try Probes.schemaAndRegex(); print("CASE schema_regex_operational_errors passed") diff --git a/Tools/WasmAudit/Sources/WasmAuditKit/Probes.swift b/Tools/WasmAudit/Sources/WasmAuditKit/Probes.swift index 830d90b..ff89d0c 100644 --- a/Tools/WasmAudit/Sources/WasmAuditKit/Probes.swift +++ b/Tools/WasmAudit/Sources/WasmAuditKit/Probes.swift @@ -4,6 +4,7 @@ import JSON import JSONSchema import JSONStreaming import JSONPatch +import JSONHTML public enum ProbeFailure: Error { case assertion(String) } func require(_ value: Bool, _ message: String) throws { @@ -238,3 +239,65 @@ extension Probes { print("WASI_CALIBRATION parse_invocations=10 input_bytes=\(bytes.count) records=\(records) parse_logical_peak=\(peak) parse_work=\(work)") } } + +extension Probes { + /// Five-product portable rendering, finite preparation and unavailable PDF boundary. + public static func htmlRenderingAndExports() async throws { + let source = #"{"é":9007199254740993,"e\u0301":-0.00e+2,"end":"LAST-CONTENT-SENTINEL"}"# + let root = try parse(source) + let formatted = try JSONHTMLRenderer().renderStringWithReport(root) + try require(formatted.result.contains("9007199254740993") && formatted.result.contains("-0.00e+2") + && formatted.result.contains("</script>") && formatted.statistics.operation[.workUnits] > 0, "Static exact HTML") + print("CASE formatted_html_exact_scalars passed") + let interactive = try JSONHTMLRenderer(options: .init(presentation: .interactive)).renderString(root) + try require(interactive.contains("Content-Security-Policy") && interactive.contains("LAST-CONTENT-SENTINEL") + && interactive.contains("9007199254740993"), "Interactive complete bytes") + print("CASE interactive_html_complete_csp passed") + let fence = String(repeating: String(UnicodeScalar(96)), count: 3) + for value in [JSON.null, true, 1, .string("😀\u{e9}e\u{301}"), .array([]), .object(try JSONObject(members: [])), root] { + let result = try JSONExportDocument.prepareWithReport(value, options: .init(formats: [.json,.html,.plainText,.markdown,.rtf], compactJSON: true)) + let exact = try JSONWriter(options: .init(numberStyle: .preserved)).write(value) + try require(try result.result.data(for: .json) == exact, "Native artifact exact bytes") + try require(result.result.availableFormats.count == 5 && result.operation[.workUnits] > 0, "Five portable artifacts") + let markdown = String(decoding: try result.result.data(for: .markdown), as: UTF8.self) + try require(markdown.hasPrefix(fence+"json\n") && markdown.hasSuffix("\n"+fence+"\n"), "Markdown fence") + try require(String(decoding: try result.result.data(for: .rtf), as: UTF8.self).hasPrefix("{\\rtf1"), "Actual styled RTF") + } + print("CASE five_portable_prepared_formats passed") + var options = JSONHTMLRenderingOptions(); options.limits.maxDocumentBytes = 0 + var exhausted = false + do { _ = try JSONHTMLRenderer(options: options).render(root) } + catch let failure as JSONHTMLFailure { + if case .limitExceeded(.documentBytes, limit: 0, attempted: _) = failure { exhausted = true } + } + try require(exhausted, "HTML capacity ignored") + print("CASE html_finite_capacity passed") + let gate = ExportCancellationGate() + let canceled = Task { await gate.wait(); return try JSONExportDocument.prepare(root) } + canceled.cancel(); await gate.release() + var canceledCorrectly = false + do { _ = try await canceled.value } catch is CancellationError { canceledCorrectly = true } + try require(canceledCorrectly, "Portable cancellation ignored") + print("CASE export_terminal_cancellation passed") + let prepared = try JSONExportDocument.prepare(root) + #if !canImport(WebKit) + var unavailable = false + do { _ = try await prepared.preparingPDF() } + catch JSONPDFFailure.backendUnavailable { unavailable = true } + try require(unavailable && !prepared.availableFormats.contains(.pdf), "PDF backend fallback") + #else + var rejected = false + do { _ = try JSONExportDocument.prepare(root, options: .init(formats: [.pdf])) } + catch JSONExportFailure.preparationRequired(.pdf) { rejected = true } + try require(rejected && !prepared.availableFormats.contains(.pdf), "Implicit PDF rendering") + #endif + print("CASE explicit_pdf_platform_boundary passed") + } +} + +private actor ExportCancellationGate { + var opened = false + var continuation: CheckedContinuation? + func wait() async { if !opened { await withCheckedContinuation { continuation = $0 } } } + func release() { opened = true; continuation?.resume(); continuation = nil } +} diff --git a/Tools/WasmAudit/Tests/WasmAuditTests/ProbesTests.swift b/Tools/WasmAudit/Tests/WasmAuditTests/ProbesTests.swift index 18a6ce4..1df5878 100644 --- a/Tools/WasmAudit/Tests/WasmAuditTests/ProbesTests.swift +++ b/Tools/WasmAudit/Tests/WasmAuditTests/ProbesTests.swift @@ -15,3 +15,5 @@ import WasmAuditKit @Test func streamCompletionAndStrictNDJSON() async throws { try await Probes.streams() } @Test func awaitedSink() async throws { try await Probes.awaitedSink() } @Test func terminalCancellation() async throws { try await Probes.cancellation() } + +@Test func exactPortableHTMLAndPreparedExports() async throws { try await Probes.htmlRenderingAndExports() }