From c79d48c15f83ef88bed9459d9abcf05e54bd0bea Mon Sep 17 00:00:00 2001 From: "Dr. Thorsten Fuchs" Date: Fri, 16 Oct 2026 11:35:47 +0200 Subject: [PATCH 1/2] Add deterministic, native-verified schema examples (S3) (#39) Add opt-in JSONSchemaExamples with deterministic exact construction, versioned digests and complete original-root native certification. Preparation and each generation enforce finite cumulative accounting; incomplete search and operational errors remain explicit. Preserve composition, Navigation, the empty dependency graph and existing platform/language contracts. Native behavior, safety, documentation and reproduction plus Linux ARM64/x86_64 behavior and safety passed on the reviewed head. Available WASI and Apple SDK/simulator evidence retains its stated device and runtime limits. Reviewed head: 75b4e4fa4e5181deb1ffd361121c97fdf71684eb Native: https://github.com/ThorFuchs/Swift-JSON/actions/runs/38833969762 Linux: https://github.com/ThorFuchs/Swift-JSON/actions/runs/38833969746 Qualified source archive SHA-256: be2625ce4a03d71dd5dc1009640002a78111dbc0391322cbfc5431511dc40fa2 --- AGENTS.md | 6 +- Documentation/Architecture/API.md | 59 +- .../Architecture/ExtractionManifest.json | 25 + Documentation/Architecture/Release1.6/API.md | 112 + .../Architecture/Release1.6/Capabilities.json | 329 +++ .../Architecture/Release1.6/Decisions.md | 60 + .../Architecture/Release1.6/G1API.md | 83 + .../Release1.6/G1Qualification.md | 109 + .../Architecture/Release1.6/G2Determinism.md | 100 + .../Release1.6/G2Qualification.md | 90 + .../Architecture/Release1.6/G3Construction.md | 61 + .../Release1.6/G3Qualification.md | 112 + .../Architecture/Release1.6/G4Construction.md | 46 + .../Release1.6/G4Qualification.md | 100 + .../Release1.6/G5Qualification.md | 78 + .../Architecture/Release1.6/G5Resources.md | 82 + .../Release1.6/G6Documentation.md | 65 + .../Release1.6/G6Qualification.md | 78 + .../Release1.6/G7Qualification.md | 126 + .../Release1.6/G8CorpusLifetime.md | 38 + .../Architecture/Release1.6/G8Integration.md | 42 + .../Release1.6/G8Qualification.md | 76 + Documentation/Architecture/Release1.6/Plan.md | 88 + .../Architecture/Release1.6/Progress.json | 437 ++++ .../Architecture/Release1.6/Reconciliation.md | 104 + .../Release1.6/SchemaExamplesLimits.md | 49 + Documentation/Architecture/ResourceLimits.md | 16 + Documentation/CI/README.md | 68 +- Documentation/DocumentationTooling.md | 40 +- .../Qualification/TestDiscovery.json | 16 +- Documentation/README.md | 8 +- Examples/Consumer/Package.swift | 1 + Examples/Consumer/README.md | 5 + .../Sources/Consumer/READMEExamples.swift | 11 + .../CertificateIsNotConstructible.swift | 3 + .../CompileNegative/PlanIsPrivate.swift | 3 + Examples/SchemaExamplesConsumer/Package.swift | 15 + Examples/SchemaExamplesConsumer/README.md | 78 + .../DocumentationExamples.swift | 164 ++ .../SchemaExamplesConsumer/G3Acceptance.swift | 76 + .../SchemaExamplesConsumer/G4Acceptance.swift | 101 + .../G7Qualification.swift | 44 + .../Sources/SchemaExamplesConsumer/main.swift | 101 + Package.swift | 3 + README.md | 71 +- Scripts/README.md | 10 +- Scripts/build_documentation.py | 6 + Scripts/build_documentation_site.py | 11 +- Scripts/check_schema_example_determinism.py | 77 + Scripts/ci.py | 67 +- Scripts/coverage.py | 2 +- Scripts/documentation_catalog.py | 2 +- Scripts/qualify_schema_example_advanced.py | 99 + Scripts/qualify_schema_example_generation.py | 79 + Scripts/qualify_schema_example_lifetime.py | 70 + Scripts/qualify_schema_example_matrix.py | 41 + Scripts/qualify_schema_example_ownership.py | 62 + Scripts/qualify_schema_examples.py | 95 + Scripts/schema_example_documentation.py | 70 + Scripts/test_qualification_harness.py | 28 + .../verify_schema_example_documentation.py | 70 + Scripts/wasm_support.py | 2 +- Sources/JSONCLIEngine/CLIBuildIdentity.swift | 2 +- .../Compilation/JSONSchemaCompiler.swift | 6 + .../Compilation/JSONSchemaQualifiedNode.swift | 62 + .../AdvancedConstruction.md | 54 + .../DeterministicConstruction.md | 87 + .../Documentation.docc/JSONSchemaExamples.md | 44 + .../Documentation.docc/PoliciesAndLimits.md | 107 + .../Documentation.docc/TypedConstruction.md | 93 + .../Documentation.docc/VerifiedCandidates.md | 95 + .../JSONSchemaExampleCorpus.swift | 22 + .../JSONSchemaExampleGenerator.swift | 267 ++ .../JSONSchemaExampleOptions.swift | 87 + .../JSONSchemaExampleResult.swift | 55 + .../SchemaExampleConstraints.swift | 70 + .../SchemaExampleConstructor.swift | 256 ++ .../SchemaExampleDecimal.swift | 154 ++ .../SchemaExampleDeterminism.swift | 190 ++ .../SchemaExamplePattern.swift | 70 + .../SchemaExamplePrimitives.swift | 131 + Tests/Fixtures/SchemaExamples/Manifest.json | 62 + .../SchemaExamples/S3-G7-seeded-v1.json | 934 +++++++ .../S3-integral-correction-v1.json | 2152 +++++++++++++++++ .../SchemaExamples/S3-original-v1.json | 2147 ++++++++++++++++ .../AdvancedConstructionTests.swift | 245 ++ .../DeterminismTests.swift | 256 ++ .../LifecycleAndAccountingTests.swift | 277 +++ .../PreparationAndCertificationTests.swift | 253 ++ .../QualificationMatrixTests.swift | 59 + .../TypedConstructionTests.swift | 248 ++ .../ArrayFrameSendable.swift | 4 + .../FrameSendable.swift | 4 + .../MoveAndShare.swift | 5 + .../ObjectFrameSendable.swift | 4 + .../RandomCopy.swift | 3 + .../RandomSendable.swift | 4 + .../TraceCopy.swift | 3 + .../TraceSendable.swift | 4 + .../TransportSendable.swift | 4 + 100 files changed, 12641 insertions(+), 49 deletions(-) create mode 100644 Documentation/Architecture/Release1.6/API.md create mode 100644 Documentation/Architecture/Release1.6/Capabilities.json create mode 100644 Documentation/Architecture/Release1.6/Decisions.md create mode 100644 Documentation/Architecture/Release1.6/G1API.md create mode 100644 Documentation/Architecture/Release1.6/G1Qualification.md create mode 100644 Documentation/Architecture/Release1.6/G2Determinism.md create mode 100644 Documentation/Architecture/Release1.6/G2Qualification.md create mode 100644 Documentation/Architecture/Release1.6/G3Construction.md create mode 100644 Documentation/Architecture/Release1.6/G3Qualification.md create mode 100644 Documentation/Architecture/Release1.6/G4Construction.md create mode 100644 Documentation/Architecture/Release1.6/G4Qualification.md create mode 100644 Documentation/Architecture/Release1.6/G5Qualification.md create mode 100644 Documentation/Architecture/Release1.6/G5Resources.md create mode 100644 Documentation/Architecture/Release1.6/G6Documentation.md create mode 100644 Documentation/Architecture/Release1.6/G6Qualification.md create mode 100644 Documentation/Architecture/Release1.6/G7Qualification.md create mode 100644 Documentation/Architecture/Release1.6/G8CorpusLifetime.md create mode 100644 Documentation/Architecture/Release1.6/G8Integration.md create mode 100644 Documentation/Architecture/Release1.6/G8Qualification.md create mode 100644 Documentation/Architecture/Release1.6/Plan.md create mode 100644 Documentation/Architecture/Release1.6/Progress.json create mode 100644 Documentation/Architecture/Release1.6/Reconciliation.md create mode 100644 Documentation/Architecture/Release1.6/SchemaExamplesLimits.md create mode 100644 Examples/SchemaExamplesConsumer/CompileNegative/CertificateIsNotConstructible.swift create mode 100644 Examples/SchemaExamplesConsumer/CompileNegative/PlanIsPrivate.swift create mode 100644 Examples/SchemaExamplesConsumer/Package.swift create mode 100644 Examples/SchemaExamplesConsumer/README.md create mode 100644 Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/DocumentationExamples.swift create mode 100644 Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G3Acceptance.swift create mode 100644 Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G4Acceptance.swift create mode 100644 Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G7Qualification.swift create mode 100644 Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/main.swift create mode 100644 Scripts/check_schema_example_determinism.py create mode 100644 Scripts/qualify_schema_example_advanced.py create mode 100644 Scripts/qualify_schema_example_generation.py create mode 100644 Scripts/qualify_schema_example_lifetime.py create mode 100644 Scripts/qualify_schema_example_matrix.py create mode 100644 Scripts/qualify_schema_example_ownership.py create mode 100644 Scripts/qualify_schema_examples.py create mode 100644 Scripts/schema_example_documentation.py create mode 100644 Scripts/verify_schema_example_documentation.py create mode 100644 Sources/JSONSchema/Compilation/JSONSchemaQualifiedNode.swift create mode 100644 Sources/JSONSchemaExamples/Documentation.docc/AdvancedConstruction.md create mode 100644 Sources/JSONSchemaExamples/Documentation.docc/DeterministicConstruction.md create mode 100644 Sources/JSONSchemaExamples/Documentation.docc/JSONSchemaExamples.md create mode 100644 Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md create mode 100644 Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md create mode 100644 Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md create mode 100644 Sources/JSONSchemaExamples/JSONSchemaExampleCorpus.swift create mode 100644 Sources/JSONSchemaExamples/JSONSchemaExampleGenerator.swift create mode 100644 Sources/JSONSchemaExamples/JSONSchemaExampleOptions.swift create mode 100644 Sources/JSONSchemaExamples/JSONSchemaExampleResult.swift create mode 100644 Sources/JSONSchemaExamples/SchemaExampleConstraints.swift create mode 100644 Sources/JSONSchemaExamples/SchemaExampleConstructor.swift create mode 100644 Sources/JSONSchemaExamples/SchemaExampleDecimal.swift create mode 100644 Sources/JSONSchemaExamples/SchemaExampleDeterminism.swift create mode 100644 Sources/JSONSchemaExamples/SchemaExamplePattern.swift create mode 100644 Sources/JSONSchemaExamples/SchemaExamplePrimitives.swift create mode 100644 Tests/Fixtures/SchemaExamples/Manifest.json create mode 100644 Tests/Fixtures/SchemaExamples/S3-G7-seeded-v1.json create mode 100644 Tests/Fixtures/SchemaExamples/S3-integral-correction-v1.json create mode 100644 Tests/Fixtures/SchemaExamples/S3-original-v1.json create mode 100644 Tests/JSONSchemaExamplesTests/AdvancedConstructionTests.swift create mode 100644 Tests/JSONSchemaExamplesTests/DeterminismTests.swift create mode 100644 Tests/JSONSchemaExamplesTests/LifecycleAndAccountingTests.swift create mode 100644 Tests/JSONSchemaExamplesTests/PreparationAndCertificationTests.swift create mode 100644 Tests/JSONSchemaExamplesTests/QualificationMatrixTests.swift create mode 100644 Tests/JSONSchemaExamplesTests/TypedConstructionTests.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/ArrayFrameSendable.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/FrameSendable.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/MoveAndShare.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/ObjectFrameSendable.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/RandomCopy.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/RandomSendable.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/TraceCopy.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/TraceSendable.swift create mode 100644 Tests/SchemaExamplesCompilerFixtures/TransportSendable.swift diff --git a/AGENTS.md b/AGENTS.md index 7e36c49..8ae3d86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,8 +4,10 @@ Swift-JSON requires Swift tools/compiler 6.4+, Swift language mode 6 and macOS/SDK 27+. Read README.md, CONTRIBUTING.md and the relevant contracts in Documentation/Architecture before changing behavior. -- Keep eight public products: JSON, JSONSchema, JSONStreaming, JSONPatch, JSONHTML, - JSONBridges, JSONNavigation and the swift-json executable. JSONFilterRuntime and JSONCLIEngine are package-private. +- Keep nine public products: JSON, JSONSchema, JSONSchemaExamples, JSONStreaming, + JSONPatch, JSONHTML, JSONBridges, JSONNavigation and the swift-json executable. + JSONSchemaExamples is opt-in and depends on JSON and JSONSchema only. + JSONFilterRuntime and JSONCLIEngine are package-private. JSONHTML and opt-in JSONBridges depend on JSON only; core never re-exports the bridges. The approved CLI and E2 additions preserve historical product contracts. JSONNavigation is an opt-in product candidate depending on JSON only; merge/release remain human-gated. diff --git a/Documentation/Architecture/API.md b/Documentation/Architecture/API.md index 55b5292..fb8fd0e 100644 --- a/Documentation/Architecture/API.md +++ b/Documentation/Architecture/API.md @@ -1,7 +1,7 @@ # Public API contract This document describes the implemented public interface. Native compiler symbol -graphs supply the complete declarations in the seven DocC catalogs. Examples +graphs supply the complete declarations in the eight DocC catalogs. Examples in the [README](../../README.md) are compiled and executed by the public consumer. ## Products and ownership @@ -18,7 +18,8 @@ in the [README](../../README.md) are compiled and executed by the public consume | JSONHTML | JSONHTMLRenderer, JSONHTMLRenderingOptions, JSONHTMLLimits, JSONHTMLRenderReport, JSONHTMLAnnotation, JSONExportDocument, JSONExportOptions, JSONExportReport | Structured forms/cards and bounded native previews, 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, HTML and Bridges depend on JSON. There are eight candidate public products: seven libraries and the swift-json executable, +JSONRegexRuntime. Streaming, Patch, HTML, Bridges and Navigation depend on JSON. +JSONSchemaExamples depends only on JSON and JSONSchema. There are nine public products: eight libraries and the swift-json executable, and no external root-package dependencies. No HTTP, MCP or documentation-tool dependency is introduced. JSONHTML conditionally uses Apple SDK frameworks for explicit native transfer/PDF preparation; the portable value, schema, Patch and streaming contracts do not acquire UI semantics. @@ -233,13 +234,53 @@ is neither native validation of an instance nor a service-acceptance guarantee. and all phase accounting are specified in [Providers](Release1.4/Providers.md) and the frozen [profile table](Release1.4/ProviderProfiles.json). No new product, SDK client or Apple type is added. -## Best-effort diagnostics and versioned CLI identity - -With best-effort schema output, an optional diagnostic-byte refusal returns ordinary -invalidity with `diagnosticsComplete == false`; it does not poison the active work -ledger. Work, retention, annotation exhaustion and cancellation remain operational -errors. Strict diagnostic output still throws on the same byte bound. - +## Schema examples (1.6 G4 development) + +The optional JSONSchemaExamples product prepares an immutable document/explicit +registry/compiled-plan snapshot. `JSONSchemaExampleGenerator(document:registry:options:)` +and `generate(seed:profile:)` offer exact declared candidates and compiler-qualified +static reference targets. Every success contains the actual native valid verdict +for the original root. Minimal scans stable exact declarations; demonstration +uses metered SplitMix64 rejection sampling and circular scans. An immutable exact +`JSONSchemaExampleCorpus` supplies candidates for true/empty schemas. Returned +objects/numbers use scalar order and normalized exact spelling. Conservative +typed construction covers the qualified foundational subset. G4 adds conservative +contains minima and a small anchored ASCII pattern-proposal grammar. Native +validation retains conditional, dependency, regex and evaluated-location semantics. +General constraint +solving, arbitrary regex inversion and dynamic construction remain incomplete. + +`JSONSchemaExampleFailure` distinguishes unsupported construction, searchExhausted, +reviewed trivial provenUnsatisfiable and invalidConfiguration. Core operational +failures and cancellation propagate without retries. Preparation accounting is +separate from the shared per-generation ledger, which includes all completed +invalid checks. Discarded validation ownership is released without refunding work. Logical +retention is not process RSS. +The result binds the versioned snapshot, ordered corpus, all generation/compiler/ +evaluator caps and flags, and complete abstract trace with SHA-256 digests. Trace +bytes share diagnosticBytes with native diagnostics; exhaustion never truncates a +success. See [the G3 constructive subset](Release1.6/G3Construction.md), +[the G4 advanced boundary](Release1.6/G4Construction.md), +[the capability ledger](Release1.6/Capabilities.json), [the G2 byte transports](Release1.6/G2Determinism.md), the historical +[G1 API decision](Release1.6/G1API.md), and the +[compiled public consumer](../../Examples/SchemaExamplesConsumer/README.md). + +## Optional verified schema examples (1.6 development) + +JSONSchemaExamples adds an immutable prepared generator over admitted documents and +explicit offline registries. Every returned value has a completed native root verdict, +with exact JSON semantics, cumulative generation statistics and versioned provenance. +It does not alter JSON/JSONSchema APIs, policies, products or dependencies. + +See the [current public declarations and failures](Release1.6/API.md), +[supported construction subset](../../Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md), +[resource scope](Release1.6/SchemaExamplesLimits.md) and +[compiled public consumer](../../Examples/SchemaExamplesConsumer/README.md). +Preparation and generation use separate finite ledgers. All attempts within a generation +share one ledger; false verdicts consume work, operational errors/cancellation terminate +without a candidate or successful report, and search exhaustion is not a proof. +The [combined-source G8 record](Release1.6/G8Qualification.md) describes qualification, +review and platform limits. Merge and release approval remain separate. The CLI's human version reports the candidate/release source version. JSON metadata includes `revisionKind: "source-sha256"` and `revision: "sha256:"`. This is the SHA-256 of the ordered Package.swift plus Sources content manifest, excluding only diff --git a/Documentation/Architecture/ExtractionManifest.json b/Documentation/Architecture/ExtractionManifest.json index 14aa49e..e0e2939 100644 --- a/Documentation/Architecture/ExtractionManifest.json +++ b/Documentation/Architecture/ExtractionManifest.json @@ -249,6 +249,28 @@ "Scripts/qualify_schema_providers.py", "Tools/CLIQualification/Sources/ProviderConsumer" ] + }, + { + "repository": "SwiftJSON-SchemaExamples-S3-Codex-Wave-v1.0 briefing packet", + "archive_sha256": "f67ec2f5d0bfd223f54a3577f93c03951a7992ecab524910ba44f6bc8621c4a1", + "original_path": "Reference/SplitMix64.swift and Reference/SplitMix64Check.swift", + "destination": "Sources/JSONSchemaExamples/SchemaExampleDeterminism.swift", + "disposition": "SplitMix64 constants, wrapping steps and rejection threshold adapted into invocation-owned noncopyable state with cumulative native metering; standalone shims not used. Sixteen golden draws retained in real-engine tests.", + "attribution": "Apache-2.0 SPDX header retained; original Swift-JSON integration code Apache-2.0.", + "validation": "Tests/JSONSchemaExamplesTests/DeterminismTests.swift; independent stdlib transport oracle and public consumer", + "evidence": "Release1.6 G2 qualification; actual native/safety/docs receipts" + }, + { + "repository": "SwiftJSON-SchemaExamples-S3-Codex-Wave-v1.0 briefing packet", + "archive_sha256": "f67ec2f5d0bfd223f54a3577f93c03951a7992ecab524910ba44f6bc8621c4a1", + "original_path": "Fixtures/cases.json", + "original_sha256": "6f2bfd4fdb7c7e206b6570c2156ca8e248831840733b0258aa8cd5938b35649f", + "destination": "Tests/Fixtures/SchemaExamples", + "disposition": "Original 44-case input retained byte-for-byte alongside the independently reviewed G0 integral-number correction (155 witnesses). G3 selects exactly 24 must-generate IDs plus two sound proofs; 18 G4 cases remain separate. No fixture expected verdict was lowered for the generator.", + "amended_sha256": "5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3", + "attribution": "User-authored packet fixture input; original integration/tests Apache-2.0. Packet source/provenance retained separately.", + "validation": "TypedConstructionTests.swift and public G3Acceptance consumer; strict native compiler/evaluator authority", + "evidence": "Release1.6/G3 qualification and materialized per-case receipts" } ], "original_implementation_notes": [ @@ -271,7 +293,10 @@ "1.4 C4 immutable IR/function/pattern metadata, invocation closures/thunks, continuations, scalar string and format helpers, root-provenance paths and transactional mutation are original Apache-2.0 code. No eager upstream evaluator, Foundation regex or approximate arithmetic was copied.", "1.4 C5 stream/offline-schema adapters, checked sink handoff, command help and consumers are original Apache-2.0 code. Five NewInputs fixtures are copied byte-for-byte from the maintainer CLI briefing pack 43f8819c57e3d93bc6f7cf561d2d111442e089f2973cb7c62e16e3bd3fd73d0d; these authored data fixtures introduce no runtime dependency. Missing provider helper prerequisites remain blocked rather than adapted or silently rewritten.", "1.4 checked schema resource lookup and its missing/quota/cancellation/install regressions are original Apache-2.0 code. Supported app/framework/command layouts retain all nine official resource pins; no unbounded directory scan, fallback engine or external dependency is introduced.", + "1.6 S3 G4 contains-minimum proposals and the small anchored ASCII proposal scanner are original Apache-2.0 Swift code. Native compiler-qualified references and complete native regex/schema evaluation remain authoritative; no matcher, resolver, engine source or dependency is adapted. The original/amended S3 fixtures retain their pinned byte identities.", "1.5.1 restores 126 original Apache-2.0 test functions and qualification tools from Swift-JSON 1.2.1 (e8b687f822c577311156b13bf1d15c2d5620b84e), with file hashes and stable function identities in Qualification/Release1.5.1/Restoration.json. The diagnostic omission and Git-free provenance repairs are restored original code; current seven-product harness reconciliation and deterministic CLI identity are original contributions. No new upstream adaptation or dependency.", + "1.6 S3 G7 seeded grammar, scalar-key permutation tests, public replay consumer and strict cardinality/matrix harness are original Apache-2.0 contributions. The 64-schema authored fixture has SHA-256 cb55604183a6db7df5fa968db283e7abeae794277d2b0cef033b0bdb92e615cb. Independent python-jsonschema 4.26.0 is research qualification only, with exact numeric binding; no Python runtime dependency or copied validator/engine is distributed.", + "1.6 S3 G8 materializes the unchanged synthetic corpus per caller access, replacing process-lifetime stored JSON payloads exposed by Linux ASan/LSan. The public process-lifetime consumer and strengthened exact/value-copy test are original Apache-2.0 qualification code; no suppression, runtime fallback, new dependency or corpus/digest change is introduced.", "Composition reference-load fixtures, serial descriptor staging, public-only adapter, capacity/terminal tests and strict CI receipt checks are original Apache-2.0 code. Generated JSON graphs introduce no upstream protocol fixtures, engine or dependency. Core public APIs, finite profiles and accounting weights are unchanged." ], "jsonhtml_export_original_contributions": { diff --git a/Documentation/Architecture/Release1.6/API.md b/Documentation/Architecture/Release1.6/API.md new file mode 100644 index 0000000..39377f9 --- /dev/null +++ b/Documentation/Architecture/Release1.6/API.md @@ -0,0 +1,112 @@ +# JSONSchemaExamples current public contract + +This is the current S3 interface through G6, superseding the provisional G1 API +notes where later accepted increments differ. Historical decisions and receipts +are preserved. It is an opt-in 1.6 development product; G7/G8 and main repair +integration remain open. Native compiler symbol graphs and source declarations +are the API authority. There is no public mutable plan, evaluator, meter or result +initializer, and no new root-package dependency. + +## Prepare, generate and inspect + +| API | Actual role and defaults | +| --- | --- | +| `JSONSchemaExampleGenerator(document:registry:options:) throws` | Admitted JSONSchemaDocument, explicit registry default `.empty`, options default `.init()`; compile/normalize/hash once under one preparation ledger | +| `generate(seed:profile:) throws -> JSONSchemaExampleResult` | Seed UInt64 default 0; profile default `.minimal`; independent cumulative invocation; returns only after native valid verdict and complete trace/hash | +| `document`, `registry`, `options` | Immutable original preparation inputs/policies; no inferred format/default/registry behavior | +| `preparationStatistics` | Separate compilation-domain counts, including snapshot/corpus/policy work; not part of the generation report | +| `sourceSnapshotDigest`, `corpusDigest`, `policyDigest` | Lowercase SHA-256 identities of the documented separate transports | + +The generator is checked Sendable. Mutable construction frames, PRNG, trace and +validation state belong to one synchronous call. The compiled root is private and +retained immutable. Caller document/registry/corpus construction precedes preparation. +Returned values have normalized exact numbers, scalar-sorted object insertion order +and preserved array nesting; mathematical/scalar semantics are those of JSON. + +## Options and profiles + +`JSONSchemaExampleOptions(limits:maximumAttempts:corpus:compilationOptions:validationOptions:)` +uses balanced generation limits, maximumAttempts **16**, corpus **.synthetic** and +native JSONSchemaOptions defaults for both compilation and validation. Options are +values checked when preparing a generator. Changing a caller's options afterward +does not mutate an existing preparation. + +* Generation limits intersect validation limits per metric. All 49 entries remain + finite and contribute to policy identity. See [limits](SchemaExamplesLimits.md). +* Compilation options remain separate, including any format assertion selected at + compilation. Validation output, diagnostic policy, annotations and assertion + settings are forwarded unchanged. Required vocabulary assertions still apply. +* `maximumAttempts` must be positive and strictly below UInt64.max. It bounds + completed candidate validations, including false verdicts, without promising + that every search reaches the limit or finds an existing witness. +* `.minimal` uses stable declared order and conservative small typed proposals, + with no random draws. It is not a global minimization guarantee. +* `.demonstration` uses seeded SplitMix64 candidate/corpus choices. Some inputs + produce the same value for every seed. Every raw rejected draw is charged. +* `JSONSchemaExampleCorpus(values:)` stores exact ordered caller JSON without + admission; preparation checks/normalizes it and rejects an empty corpus. The + synthetic ordered values are null, false, 0, "example", [], {}. The read-only + `synthetic` property materializes these values per access; caller lifetime owns + their storage, before checked preparation. Corpora are hints. + +Candidate role order is **const, default, enum, examples** (ASCII keyword order). +Array order is semantic. Discovery follows qualified alternatives/static edges; +typed construction then offers bounded variations. Defaults and examples can be +invalid; nothing is inserted or repaired. Complete original-root validation alone +establishes success. See [supported subset](../../../Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md) +and [all 59 keyword classifications](Capabilities.json). General satisfiability, +regex inversion, triggered dependency/key synthesis and constructive dynamic scope +are incomplete. No boundary profile, AI, online resolver or interop converter is added. + +## Result and provenance + +JSONSchemaExampleResult exposes immutable `value`, actual native `validation`, +`seed`, `profile`, `attempts`, `statistics`, `validationOptions` and +`compilationOptions`, plus these identities: + +| Properties | Current identity | +| --- | --- | +| generatorVersion | s3-candidates-v4 | +| schemaDigestAlgorithm / sourceSnapshotDigest | swift-json-schema-snapshot-v1 / source and ordered registry hash | +| corpusDigestAlgorithm / corpusDigest | swift-json-example-corpus-v1 / admitted ordered corpus hash | +| policyDigestAlgorithm / policyDigest | swift-json-example-policy-v1 / all three 49-cap profiles, attempt ceiling and policy flags | +| rngAlgorithm / randomDraws | splitmix64-v1 / every raw draw, including rejection | +| traceDigestAlgorithm / traceDigest / traceEventCount | swift-json-example-trace-v2 / complete abstract decision hash and event count | + +Transports exclude source maps, unavailable original source bytes and caller time. +They are not RFC8785/JCS. Registry order contributes to source identity. Seed alone +cannot bind a result; retain implementation commit, versions and all input identities. +[G2 transports](G2Determinism.md), [G3 trace additions](G3Construction.md) and the +[G5 accounting correction](G5Resources.md) preserve historical versions separately. +Statistics snapshot certification and conservative staging ownership, before transfer; +logical retained bytes/peaks are not heap size or RSS. WorkUnits includes subsystem work. + +## Failures and semantic authority + +| Failure | Meaning | +| --- | --- | +| unsupportedFeature(candidateConstruction / dynamicReference / cyclicReference) | Current constructor lacks a proposal or reached unsupported scope/active cycle; no validity conclusion | +| searchExhausted(attempts:) | All offered candidates rejected or finite ceiling reached; not an unsatisfiability proof | +| provenUnsatisfiable(falseSchema / notTrue) | Only reviewed trivial root false or active root `not` of true/empty schema | +| invalidConfiguration(attemptLimit / emptyCorpus) | Invalid generator configuration before preparation/search | +| Native compilation/evaluation/resource error or CancellationError | Operational failure; terminal, never ordinary false validation or accepted incompleteness | + +No failed call publishes a value, complete trace or successful work report. A caught +operational error cannot resume its internal invocation; a later public call is an +independent operation. Immutable preparation remains reusable. Public consumers must +not invent failed-work statistics when composing separate calls. + +## Compiled examples and integration + +[README examples](../../../README.md#generate-verified-schema-examples-16-development) +compile in the seven-library consumer. Every Swift block in all five S3 DocC guides +is synchronized with DocumentationExamples.swift and runs twice in the separate +[public consumer](../../../Examples/SchemaExamplesConsumer/README.md), alongside +15 preserved cases and two compiler negatives. Drift/missing snippets fail the lane. +Both packages import public products only; neither is a root runtime dependency. + +JSONSchemaDSL is absent from the inspected S3 Package.swift and main base. Its +independent train is untouched. A DSL equality example is deferred until a real +product is available during integration. An admitted document from any authoring +source can use this API under the same native dialect; no foreign notation is +implicitly converted. CLI, JSONHTML, AI and schema interop gain no S3 dependency. diff --git a/Documentation/Architecture/Release1.6/Capabilities.json b/Documentation/Architecture/Release1.6/Capabilities.json new file mode 100644 index 0000000..2b3e2b9 --- /dev/null +++ b/Documentation/Architecture/Release1.6/Capabilities.json @@ -0,0 +1,329 @@ +{ + "schema_version": 1, + "gate": "S3-G4", + "generator_algorithm": "s3-candidates-v4", + "trace_algorithm": "swift-json-example-trace-v2", + "semantic_authority": "Original compiled root plus completed JSONSchemaValidator result. Capability classifications describe proposal construction only.", + "groups": [ + { + "id": "type", + "keywords": [ + "type" + ], + "construction": "constructive", + "policy": "Fixed type preference, unions/intersections from active vocabularies; integer remains exact integral number.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "declared-candidates", + "keywords": [ + "const", + "enum", + "default", + "examples" + ], + "construction": "conservative", + "policy": "Exact declared candidates; defaults/examples are hints, nested const/enum proposals still require complete root validation.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "numeric", + "keywords": [ + "minimum", + "maximum", + "exclusiveMinimum", + "exclusiveMaximum", + "multipleOf" + ], + "construction": "conservative", + "policy": "Exact bounded decimal step/rounding; dense alignment can exhaust finite digit/work limits. Multiple conjunctive divisors are certified natively rather than solved generally.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "strings", + "keywords": [ + "minLength", + "maxLength" + ], + "construction": "constructive", + "policy": "Unicode-scalar lengths; bounded admitted corpus/filler tokens.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "format", + "keywords": [ + "format" + ], + "construction": "conservative", + "policy": "Fixed email token when native assertion applies; annotation does not assert or imply a type. Other formats are native certification only.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "objects", + "keywords": [ + "properties", + "required", + "additionalProperties", + "minProperties", + "maxProperties" + ], + "construction": "conservative", + "policy": "Union of exact required keys, every conjunct scoped separately, optional omission and finite declared/synthetic fill names.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "arrays", + "keywords": [ + "prefixItems", + "items", + "minItems", + "maxItems" + ], + "construction": "constructive", + "policy": "Explicit iterative frames, checked lengths and positional indices; nested arrays do not flatten.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "uniqueness", + "keywords": [ + "uniqueItems" + ], + "construction": "conservative", + "policy": "Finite leaf variations; only native exact deep comparison certifies uniqueness. No complete cardinality solver.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "composition", + "keywords": [ + "allOf", + "anyOf", + "oneOf" + ], + "construction": "conservative", + "policy": "Positive conjunction hints and finite alternative child proposals. No schema merge or branch-local success. Native exactly-one/conditional/annotation semantics remain authoritative.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "static-resources", + "keywords": [ + "$ref", + "$id", + "$schema", + "$anchor", + "$defs", + "definitions", + "dependencies", + "$vocabulary", + "$comment" + ], + "construction": "compiler_authority", + "policy": "Compiler-qualified resource/node handles; no URI guessing or implicit definition rooting. Repeated static targets are DAG sharing; same-instance ancestor cycles are unsupported. Legacy definitions/dependencies remain non-applicative containers whose referenced children retain compiler locations.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "dynamic", + "keywords": [ + "$dynamicRef", + "$dynamicAnchor" + ], + "construction": "unsupported_dynamic_construction", + "policy": "A reached scope-dependent reference fails explicitly. Omitting an optional dynamic child may produce a native-certified finite witness; that does not qualify constructive dynamic-scope navigation.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "native-only", + "keywords": [ + "patternProperties", + "propertyNames", + "dependentRequired", + "dependentSchemas", + "unevaluatedProperties", + "unevaluatedItems", + "if", + "then", + "else" + ], + "construction": "native_certification_only", + "policy": "No constructive interpretation of these keywords. Optional omissions and finite whole-root proposals can satisfy the pinned fixtures; only complete native evaluation establishes conditionals, dependencies, key patterns/names and evaluated locations. No general triggered-constraint solver.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "not", + "keywords": [ + "not" + ], + "construction": "native_certification_only", + "policy": "Only active root not-true/not-empty has a reviewed trivial unsatisfiability proof; other negations are native certification only.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "annotations", + "keywords": [ + "title", + "description", + "deprecated", + "readOnly", + "writeOnly", + "contentEncoding", + "contentMediaType", + "contentSchema" + ], + "construction": "metadata_only", + "policy": "Never executed, inserted, repaired, resolved online or treated as validation authority.", + "evidence_tests": [ + "g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds", + "g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes" + ] + }, + { + "id": "pattern-proposals", + "keywords": [ + "pattern" + ], + "construction": "conservative", + "policy": "Fully anchored printable ASCII literals, escaped regex punctuation, single ASCII-range classes and exact repetition only. Never a regex matcher; unsupported syntax may use finite corpus/filler proposals. Native regex compilation/matching and quotas remain authoritative.", + "evidence_tests": [ + "g4AnchoredLiteralRangeAndExactRepeatProposalsAreNativeCertified", + "g4UnsupportedRegexSyntaxNeverPretendsToBeAnInverter", + "g4RegexOperationalFailureIsTerminalRatherThanAnInvalidCandidate", + "g4PatternScannerHasExactWorkAndPreallocationBoundaries", + "g4CancellationDuringPatternScanningLatchesTheInvocation" + ] + }, + { + "id": "contains-proposals", + "keywords": [ + "contains", + "minContains", + "maxContains" + ], + "construction": "conservative", + "policy": "Minimum positive-match proposals combined with prefix/items constraints per conjunct. Native validation counts all matches/maxima and annotations. Disjoint partitions and excluding surplus matches are not solved.", + "evidence_tests": [ + "g4ContainsDefaultZeroAndInactiveSiblingLimitsRemainDistinct", + "g4ContainsPrefixAndUniqueConstraintsShareTheRootCertificate", + "g4ExtraContainsMatchesRemainHonestSearchExhaustion", + "g4ContainsCardinalityIsCheckedBeforeItemStaging" + ] + } + ], + "fixture_matrix": { + "source": "Tests/Fixtures/SchemaExamples/S3-integral-correction-v1.json", + "sha256": "5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3", + "g3_must_generate_ids": [ + "boolean-true", + "null-kind", + "string-length", + "exact-integer-beyond-2pow53", + "exact-decimal-multiple-of", + "numeric-exclusive-near-zero", + "object-required-and-null", + "unicode-exact-scalar-keys", + "undeclared-required-key", + "object-min-max-properties", + "array-prefix-items", + "array-unique", + "enum", + "const-exact-decimal", + "anyof", + "allof-object-composition", + "local-ref", + "format-annotation", + "format-assertion", + "invalid-default-annotation", + "invalid-examples-annotation", + "false-nested-schema", + "object-number-key-strings", + "required-with-additional-properties-false" + ], + "profiles": [ + "minimal", + "demonstration" + ], + "seeds": [ + "0", + "1", + "42", + "18446744073709551615" + ], + "expected_successful_invocations_per_process": 192, + "g3_proof_ids": [ + "boolean-false", + "not-true" + ], + "g4_cases": 18, + "g4_status": "passed native increment; full release/platform gates remain open", + "g4_must_generate_ids": [ + "array-contains", + "condition-then-else", + "dependent-required", + "dependent-schema", + "pattern-properties", + "property-names", + "unevaluated-properties-composition", + "simple-regex", + "mincontains-with-prefixitems", + "array-mixed-evaluation" + ], + "g4_may_fail_ids": [ + "oneof-overlap", + "recursive-tree", + "dynamic-ref", + "conflicting-allof", + "unfulfillable-unique-enum", + "external-offline-ref", + "not-string", + "oneof-overlap-limited" + ], + "g4_expected_required_successes_per_process": 80, + "g4_total_invocations_per_process": 144, + "all_native_witness_expectations": 155, + "g4_observed_successes_per_process": 112, + "g4_observed_explicit_incomplete_per_process": 32, + "g4_public_processes": 2 + }, + "limitations": [ + "No general satisfiability decision or universal must-generate promise outside the pinned subset", + "No unqualified cross-platform, RSS or controlled-throughput claim", + "Main 1.5.1 integration and G7/G8 remain separate gates" + ] +} diff --git a/Documentation/Architecture/Release1.6/Decisions.md b/Documentation/Architecture/Release1.6/Decisions.md new file mode 100644 index 0000000..718313d --- /dev/null +++ b/Documentation/Architecture/Release1.6/Decisions.md @@ -0,0 +1,60 @@ +# S3 initial decisions + +These decisions establish the implementation boundary. Exact new public declarations +remain subject to G1 real-engine compiler/consumer qualification. + +1. Prepare from an admitted JSONSchemaDocument, explicit immutable registry and + separately recorded compilation/evaluation options. Retain the compiled plan + for every final verdict. A public compiled-plan-only initializer would lose + construction/reference snapshot obligations and is deferred. +2. Use a minimal package-only JSONSchema view over compiler-resolved node/resource + identities and static edges. Existing node fields are internal. Do not make the + whole graph public/package-mutable, import the regex runtime into the generator, + duplicate URI resolution or infer targets from names. Dynamic construction is + scope-aware or explicitly unsupported; native evaluation remains authoritative. +3. Reuse the existing package validation entry with the same noncopyable + JSONWorkMeter through the entire generate invocation. Existing operation-domain + switching preserves consumed work and terminal health. Construction, numeric + work, references, rejected draws and completed false validations all consume + cumulative quotas. G5 must prove this behavior; G0 typechecking alone does not. +4. Keep preparation separately bounded. The existing package compiler entry can + borrow a preparation meter; normalized snapshot serialization and SHA-256 must + share its owned work/retention boundary. Caller-created document/registry work + precedes preparation. Do not add independent memory peaks or label them RSS. +5. Numeric synthesis uses bounded exact components/decimal routines. No dependency + on the private CLI/filter targets merely to obtain arithmetic, and no Double, + epsilon, Foundation Decimal or NSNumber authority. Any factored helper requires + review and regression coverage without changing public arithmetic semantics. +6. Retain splitmix64-v1 wrapping arithmetic only for PRNG state/mixing. Quota + counters use checked arithmetic. Every rejected uniform-selection draw is billed. + The standalone reference's native vectors are preparation evidence, not a + qualification of unmetered reference code as production runtime. +7. Specify SwiftJSON-schema-snapshot-v1 before freezing goldens: compact scalar-key + normalized-number writer bytes, unambiguous length-prefixed resource identities, + explicit ordering and separately identified policies/corpus. Original source + bytes cannot be invented from a schema document/source map. No RFC8785 claim. +8. Return a certified result only through a completed native valid verdict. Keep + searchExhausted, unsupported, sound false/not-true proofs and invalid configuration + distinct. Propagate cancellation and operational evaluator failures terminally. + Defaults/examples can supply candidates only; no repair or inferred requiredness. +9. Adopt the explicitly documented integer-fixture amendment and extra fractional + negative, retaining the original packet. Python exists only in the isolated + research/oracle environment. It never becomes a Swift runtime dependency. +10. Add one optional library product and its tests during G1; retain all current + products and private targets. The independent Dual DSL train does not gate S3. + Update exact CI discovery/manifest/DocC checks only alongside real additions; + preserve all 49 core probes and unchanged pinned official suite policies. + +Full generation accounting, cross-platform deterministic goldens, cancellation +behavior and support classifications remain unqualified until their gates execute. +Finite candidate search does not decide arbitrary schema satisfiability. Profiles +are preferences, with no globally minimal witness or universal generation guarantee. + +G5 closes decision 3 for the qualified native increment. The existing package-only +compiler/evaluator meter bridge is sufficient; no new public accumulator or error +report API is added. A generator-internal borrowed entry is the actual public +execution path and allows deterministic terminal-state/cleanup qualification. +Conservative staging reservations live through proposal/invocation boundaries; +reports snapshot certification before result ownership transfers. See G5Resources.md. +G5 removes an unmatched 32-byte trace refund without changing logical weights, +candidate choices or transport versions. Later full/platform gates remain open. diff --git a/Documentation/Architecture/Release1.6/G1API.md b/Documentation/Architecture/Release1.6/G1API.md new file mode 100644 index 0000000..dadca7c --- /dev/null +++ b/Documentation/Architecture/Release1.6/G1API.md @@ -0,0 +1,83 @@ +# G1 API and preparation decision + +Accepted before implementation on `codex/1.6-schema-examples`, base `507dbf9`. +This is a working candidate-verification increment, not the G3 constructive subset. + +The optional eighth product `JSONSchemaExamples` imports JSON and JSONSchema only. +Existing product dependencies remain unchanged. Its immutable Sendable generator +accepts `document:registry:options:`, compiles once and retains that exact offline +plan. Each `generate(seed:profile:)` invocation owns one noncopyable core meter, +intersecting generation and validation limits without changing evaluator policies. +Preparation has its own compilation meter, shared with snapshot serialization and +hashing. Caller document/registry admission precedes preparation. + +G1 accepts these public roles: + +- `JSONSchemaExampleOptions(limits:maximumAttempts:compilationOptions:validationOptions:)`. + Use existing JSONLimits rather than a second copy of its 49 metrics. A positive + finite maximumAttempts is a separate search ceiling; zero/UInt64.max are invalid. +- `JSONSchemaExampleGenerator(document:registry:options:) throws`, and + `generate(seed:profile:) throws -> JSONSchemaExampleResult`. The only current + profile is `.minimal`. Seed is recorded; G1 makes no random choices. No RNG or + corpus API is advertised before G2's algorithms are implemented. +- An immutable result containing the exact value, actual completed validation, + seed/profile, implementation version, source snapshot digest, evaluation policy, + attempt count and cumulative invocation statistics. Result initialization is + internal; callers cannot construct a purported certification through this API. +- Typed failure cases `unsupportedFeature`, `searchExhausted`, + `provenUnsatisfiable`, `invalidConfiguration`, with finite enum codes instead + of untrusted free-form messages/paths. Core schema/resource/configuration errors + and CancellationError propagate unchanged. + +Candidate order is active const, enum in declaration order, default, examples in +declaration order, then compiler-resolved static references in compiler edge order. +Boolean true and empty object schemas propose null. Each candidate is checked +against the **original prepared root**, including reference siblings. Metadata is +never inserted or repaired. Every completed false check consumes the same ledger; +operational errors terminate immediately. Discarded validation ownership is released without refunding work. Statistics +use existing logical weights; they are not RSS or allocator measurements. +Unsupported construction and reference cycles remain explicit failures. Dynamic +edges are not followed; encountering one in the searched declaration is unsupported. + +Only root false and root not-true/not-empty-object with an active applicator +vocabulary have G1 unsatisfiability proofs. No reference-cycle, invalid-candidate +or search-limit failure is presented as a proof. Other satisfiable schemas can +still fail to produce a witness until later gates. + +The package-only `JSONSchemaQualifiedNode` handle is created only by a compiled +plan or its checked child/reference edges. Its immutable view exposes raw +declaration, compiler resource URI, original document Pointer, active vocabulary +and format-assertion flag. Indexed edge inspection charges the borrowed meter. +It exposes neither mutable graph fields nor regex programs. Integer identities are +local to one plan. No new public JSONSchema declarations or URI resolver are added. +An internal compiler overload borrows the preparation meter while preserving the +original admitted document/source map, with the same compilation implementation. + +`swift-json-schema-snapshot-g1-v1` is SHA-256 over this exact transport: ASCII +`SwiftJSON-schema-snapshot-g1-v1\n`, big-endian UInt64 document count (root then +registry documents in explicit caller order), then for each document a one-byte +retrieval-URI-present marker, a length-prefixed UTF-8 retrieval URI (empty if absent), +and length-prefixed compact JSONWriter bytes (scalar key order, normalized exact +numbers). Every length is big-endian UInt64. Source maps/original lexemes are not +source text and are excluded. Registry order is deliberately part of this identity. +Policies are retained separately, unchanged; equal source digest does not imply +equal policies. This is not RFC8785, raw-source hashing or G2's final corpus/trace +transport. The G1 algorithm ID is `s3-g1-candidates-v1`. + +Rejected for G1: public graph access, caller-supplied compiled plans without a +coherent source/registry snapshot, URI guessing, opaque validation reports, broad +arithmetic/regex synthesis, unimplemented profiles, invented corpus/trace receipts, +and any dependency on the separate DSL train. Full G5 lifecycle qualification and +G7 platform/conformance qualification remain separate gates. + +## G1 resource units and review + +Each newly searched compiled declaration charges schemaNodes, rather than treating +visited identities as active evaluator frames. Pending construction handles are +bounded by evaluationFrames. Identity slots and pending handles use the existing +indexSlot (32) and frame (16) logical weights; a returned certificate reserves +128 logical bytes for its metadata. Candidate admission/evaluation uses the native +weights. Failed completed validations release their newly owned result/scratch +retention without refunding work, diagnostics or lifetime value-admission counts. +No independent peaks are added to impersonate live retention or RSS. The G1 tests +include a zero schemaNodes discovery ceiling and a shared-work exact boundary. diff --git a/Documentation/Architecture/Release1.6/G1Qualification.md b/Documentation/Architecture/Release1.6/G1Qualification.md new file mode 100644 index 0000000..1401490 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G1Qualification.md @@ -0,0 +1,109 @@ +# S3 G1 implementation and native qualification + +G1 is complete on the isolated `codex/1.6-schema-examples` branch. This closes the +public API/preparation increment, not G2–G8 or a 1.6 release gate. The implementation +commit is `6c19580dec99c9363bb4b4d1611af8b8a2bab266`; the following closure adds +maintainer records and strengthens the public consumer, without changing library +or test source. Native receipts record tested source hashes as well as Git identity. + +## Accepted implementation + +`JSONSchemaExamples` is the eighth public product and seventh library. It depends +only on JSON and JSONSchema; existing product edges and the empty root dependency +graph are unchanged. No sibling DSL, CLI, platform, resolver or regex implementation +was added. Original code is Apache-2.0, with no new upstream code adaptation. + +The package-only qualified-node handle exposes immutable declarations, active +vocabulary/format policy and checked compiler-resolved child/static-reference +edges. It exposes no mutable graph arrays, URI resolver or regex engine. Its +targets originate only in the retained plan. External access and external result +construction are rejected by actual compiler probes. + +The public generator compiles once from document plus explicit registry, retaining +separate compilation and validation policies. G1 offers const, enum, default, +examples, true/empty roots and static references. Each candidate is checked against +the original root. The successful result contains the native completed valid +verdict. Unsupported construction, bounded exhaustion, two sound trivial proofs +and generator configuration errors are distinct. Core errors/cancellation escape +terminally; no failed operation is retried with a new budget. + +Preparation shares one meter across compilation, normalized snapshot serialization +and portable SHA-256. Each generation shares one noncopyable meter across discovery +and every completed validation, including false verdicts. Discarded validation +ownership is released; work and lifetime admissions are not refunded. Counts and +peak logical bytes are neither caller instruction quotas nor process RSS. + +See [the exact declarations and resource units](G1API.md) and the +[public consumer](../../../Examples/SchemaExamplesConsumer/README.md). + +## Observed qualification + +Native environment: macOS 27.0 arm64, Xcode 27.2 build 27B5019j, Apple Swift 6.4 +`swiftlang-6.4.0.34.1`, selected SDK 27.2, language mode 6. Test binary inspection +confirms minimum macOS 27.0; the Swift Testing host-framework banner is not the +package deployment target. No compatibility flag or unsafe annotation was added. + +| Command/check | Observed result | +| --- | --- | +| `python3 Scripts/ci.py --lane schema-examples` | Exit 0 on implementation commit; all six commands passed | +| New Swift Testing target | 21 functions, zero failures; exact module identity and cardinality enforced | +| Existing JSONSchemaTests | 69 functions, 12 exact parameter invocations, zero failures | +| Pinned schema suite within existing tests | 1,301 core + 125 optional semantic + 874 format cases = 2,300 evaluated, zero failures | +| Schema policy ledger | 2,337 discovered; 37 separate policy cases, unchanged; exclusions are not evaluated successes | +| NFC qualification within existing tests | 18,853 cases / 94,265 comparisons | +| Public consumer | 12 cases; independent native verification and roundtrip of every returned value, including the offline registry case | +| Public compiler boundaries | Two intended typecheck exits 1 for package-only plan access and internal certificate initialization | +| `python3 Scripts/ci.py --lane schema-examples-safety` | Exit 0: 21 Release ASan functions, one shared-generator TSan function, strict memory-safety target/dependency build with warnings as errors | +| `python3 Scripts/ci.py --lane docs` | Exit 0: seven native DocC catalogs; 988 authored declarations documented, including all 42 new declarations and named parameters; README consumer and local static site checks | +| CI event parser checks | 19 existing parser receipts passed | +| CI negative/mutation checks | 12 acceptance/rejection checks; wrong products, back-edges, dependencies, missing functions and a focused-safety substitution rejected | + +Suite pin: `json-schema-org/JSON-Schema-Test-Suite` +`5b0ee1613e45fcc2bddac00e07c19cd49b00d8a8`. Meta-schema pin, Unicode 13 data, +upstream licenses, resource selections and policy tables are unchanged. The tests +run the pinned inputs already in this repository; no online resolution is used. +All 49 baseline finite policies and their full-lane cap probes remain required. +The full root requirements are now 440 Mac functions / 325 parameter invocations +and 16 shared-state functions. Focused G1 runs do not claim that full inventory ran. + +Private materialized records are retained under ignored `Artifacts/SchemaExamplesS3/G1`. +The return bundle contains command logs, events, source hashes, input pins and a +mapping from qualified source to the final archive. The successful implementation +native receipt is `native-committed/receipt.json`; safety is `safety-first/receipt.json`. +The strengthened consumer also runs in the final docs lane. Unchanged code evidence +is reused with file-hash comparison, not asserted from a summary alone. + +## Review findings and retained failures + +The initial full-package test compilation failed with disk-full errors before any +tests ran. Native Swift 6.4's actual `--test-product` option selects the generated +test bundle and preserves exact discovery. An invalid build-option combination +and a skip-build attempt on incomplete unrelated bundles are retained as failed +tool probes, not passes. + +One format-policy fixture initially omitted the metadata vocabulary needed to +offer its default candidate; adding that vocabulary preserved the intended format +rejection test. A real harness ambiguity after sanitizer builds was fixed by using +SwiftPM's reported consumer binary directory instead of recursively selecting from +all caches. Both failed runs and subsequent passing runs are retained. + +Review also corrected discovery accounting to charge schemaNodes, separated live +discarded-result release from cumulative work, and strengthened the offline public +consumer's independent validation/roundtrip and public remaining-work demonstration. +No test count, expected engine verdict, resource baseline or suite policy was lowered. + +## Remaining scope + +Only minimal is exposed; G1 records seed but draws no random numbers. The algorithm +is `s3-g1-candidates-v1`, with the documented `swift-json-schema-snapshot-g1-v1` +transport. The digest normalizes key order and numeric spelling; it does not recover +original order/lexemes or source text. G2 must freeze emitted-byte/corpus/trace +identity and metered random construction. No cross-platform determinism claim is +made from native-only G1 results. + +General constraint-driven synthesis, richer profiles, the 44-case generation +ledger, advanced/dynamic construction, complete G5 adversarial lifecycle proof and +full G7 Native/Linux/WASI/mobile qualification remain open. No controlled performance +or RSS result is claimed. No branch was pushed, PR opened, merge performed, tag +created or release published. Sibling work and the original main checkout were +not changed by this implementation. diff --git a/Documentation/Architecture/Release1.6/G2Determinism.md b/Documentation/Architecture/Release1.6/G2Determinism.md new file mode 100644 index 0000000..3198d54 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G2Determinism.md @@ -0,0 +1,100 @@ +# G2 determinism and accounting decision + +Accepted on the isolated S3 branch before the G2 implementation. This supersedes +G1's provisional algorithm/digest identifiers, preserving its native certification +authority and explicit unsupported cases. General synthesis remains G3/G4 work. + +`s3-candidates-v2` uses `splitmix64-v1` (the packet's wrapping constants and shifts). +Each raw draw costs 10 core work units, including rejected draws. Uniform selection +adds three units for the rejection threshold and two per comparison/modulo. The +threshold is `(0 &- bound) % bound`; zero bounds throw. Quota counters never wrap. +Mutable RNG/trace state is noncopyable and invocation-owned. Swift 6.4 initially +inferred Sendable for these internal value types; explicit unavailable Sendable +conformances now opt them out. Compiler fixtures reject copying and Sendable +requirements for the mutable helpers and accept moves plus the sharable facade. +Fixed draw/threshold/comparison costs are charged one unit at a time, including +inside a draw, so cancellation quantum one remains meaningful. No default Hasher, +locale, clock, network, callback or floating-point conversion affects decisions. + +Candidate roles use ASCII keyword order: const, default, enum, examples; then +true/empty-schema candidates and compiler-qualified static references in ASCII +keyword order ($dynamicRef with a static target, then $ref). Array order +is semantic and retained. Minimal scans arrays from zero without draws; +demonstration picks an unbiased starting offset and scans circularly, without +replacement. It also chooses from the immutable synthetic corpus for true/empty +schemas. This is seeded candidate selection, not general demonstration synthesis. +There is no type preference until typed synthesis is implemented in G3. + +Every proposed value is constructed by the existing iterative strict writer +(compact, scalar UTF-8 key order, normalized exact decimals), followed by the +existing strict parser using the SAME meter. This normalizes object insertion +order and numeric lexemes without changing scalar identity or mathematical value. +Nested arrays remain arrays; no Dictionary, Double or approximate +arithmetic intervenes. Construction/admission/validation work remains cumulative. +Discarded attempts refund only their owned live bytes, excluding retained traces. + +## Byte transports, version 1 + +All digests are lowercase SHA-256. `u64` means eight bytes, unsigned big-endian; +`field(b)` means `u64(byteCount) || b`. Literal headers include their final LF. +Text is exact UTF-8. These are Swift-JSON transports, **not RFC8785/JCS** or hashes +of unavailable original source text. No Unicode normalization is performed. + +* `swift-json-schema-snapshot-v1`: header `SwiftJSON-schema-snapshot-v1\n`, + u64 document count, root followed by registry in caller order. Each document is + one URI-presence byte (0/1), field(retrievalURI UTF-8, empty when absent), + field(compact scalar-key normalized-number strict writer bytes). Source maps, + original numeric lexemes and policies are excluded. Registry order is identity. +* `swift-json-example-corpus-v1`: header `SwiftJSON-example-corpus-v1\n`, + field(the same writer encoding of the ordered corpus as one JSON array). + The built-in corpus is `[null,false,0,"example",[],{}]`. Custom values are + admitted, normalized and hashed during preparation; caller array construction + precedes preparation. Empty corpora are rejected. Corpus order is identity. +* `swift-json-example-policy-v1`: header `SwiftJSON-example-policy-v1\n`, + u64 maximumAttempts, then three limit records in generation, compilation, + validation order. Each record is u64(49), then for every JSONResourceMetric in + declaration order field(metric rawValue UTF-8), u64(limit). Compilation flags + then validation flags are four bytes each: formatAssertion (0/1), output + (flag=0/basic=1), diagnosticPolicy (complete=0/bestEffort=1), collectAnnotations + (0/1). This binds every one of the 49 caps, including cancellation quantum. +* `swift-json-example-trace-v1`: header `SwiftJSON-example-trace-v1\n`, + fields of generatorVersion, source digest, corpus digest, policy digest (ASCII), + u64 seed, profile byte (minimal=0/demonstration=1). Events are one code byte + followed by two u64 operands: visit=1(0,0), offer=2(role,index), draw=3(raw,bound), + verdict=4(attempt,valid:0/1), finish=5(attempts,draws). Role IDs: const=0, + default=1, enum=2, examples=3, true/empty=4. No compiler-local IDs, arbitrary + schema strings, values, diagnostic text, timing or work counts enter the trace. + +Every trace byte charges diagnosticBytes, retainedBytes and work before append; +native diagnostics share that cumulative diagnosticBytes ceiling. There is no +silent truncation or partial-digest success. RNG draw events include rejected +draws. A successful trace includes the final native valid verdict and finish; +hashing and result metadata are charged before returning. Failure/cancellation +returns no certificate or accounting success. Digesting excludes its own hash +and work counts, avoiding circular definitions. Stable trace bytes are distinct +from platform-dependent diagnostic/work observations. + +Preparation shares its compiler meter with corpus admission, all transports and +hashing. Generation uses the intersection of generation/evaluation limits. +Existing iterative codec/admission frames bound depth before growth; no recursive +constructor is introduced. Logical accounting is conservative live ownership, +not allocator/RSS or caller-instruction accounting. G7 must still run these +goldens on Linux/WASI before a cross-platform qualification claim. + +## Accepted API additions and qualification boundary + +`JSONSchemaExampleOptions` adds `corpus:` (default `.synthetic`). The corpus is +`JSONSchemaExampleCorpus(values:)`; its values are immutable and caller-authored +until checked preparation. `.demonstration` is the only added profile; boundary +and typed construction remain deferred. Prepared generators expose corpusDigest +and policyDigest. Results add corpus/policy/trace algorithm IDs and digests, +rngAlgorithm, randomDraws and traceEventCount. Result initialization stays internal. +The existing public JSON and JSONSchema APIs/dependencies are unchanged. + +The focused `schema-examples-determinism` lane checks exact product/dependency +shape, all 42 generator tests in Debug and Release, implementation ownership +compiler probes and the real public consumer plus independent transport oracle. +The broader `schema-examples` lane and full release/safety lanes remain intact. +Unchanged-core suite reuse is tied to actual file path/hash sets in the G2 source +evidence map; it never upgrades a failed aggregate receipt to a passed lane. +No core metric probe or parameter/cardinality guard is removed. diff --git a/Documentation/Architecture/Release1.6/G2Qualification.md b/Documentation/Architecture/Release1.6/G2Qualification.md new file mode 100644 index 0000000..e2fa66c --- /dev/null +++ b/Documentation/Architecture/Release1.6/G2Qualification.md @@ -0,0 +1,90 @@ +# G2 native increment and evidence + +G2 adds metered SplitMix64 choices, stable exact construction and versioned +source/corpus/policy/complete-trace digests to the optional JSONSchemaExamples +product. The accepted API and normative transports are in G2Determinism.md. +G0/G1 history remains intact; G3-G8 remain open and release_ready remains false. +No existing JSON/JSONSchema production source or root dependency was changed. + +## Actual native outcomes + +On macOS 27.0 arm64, Xcode 27.2 (27B5019j), Apple Swift 6.4 +(swiftlang-6.4.0.34.1), selected SDK 27.2, language mode 6: + +* `python3 -B Scripts/ci.py --lane schema-examples-determinism --output Artifacts/SchemaExamplesS3/G2/determinism-verified` + exited 0: seven commands, exact eight-product/empty-dependency checks, + 42 Swift Testing functions in Debug and 42 in Release. The public consumer + passed 15 cases in each of two independent processes; two public access-control + negatives rejected as expected. Independent stdlib SHA-256/byte-format vectors + matched both profiles and all 49 policy fields. +* The implementation compiler qualifier accepted moving RNG state once and + sharing the immutable facade; it rejected double consumption of RNG/trace and + Sendable requirements for RNG/trace/transport (one positive, five negatives). + These trusted @testable fixtures are distinct from the public-only consumer. +* `python3 -B Scripts/ci.py --lane schema-examples-safety --output Artifacts/SchemaExamplesS3/G2/safety-verified` + exited 0: all 42 functions in Release ASan, one TSan function with 32 concurrent + invocations (plus sequential reference calls), and strict-memory-safety with + warnings as errors. Every command exited 0; no sanitizer failure was ignored. +* The broader schema-examples run evaluated all 69 existing schema functions, + 12 parameter invocations and 2300 pinned cases (1301 core, 125 optional semantic, + 874 formats), with zero failures. Its 37 policy exclusions are separate. + That aggregate run failed its stale **12-case consumer marker**, despite the + actual 15-case consumer/oracle passing. Only its separately passed unchanged-core + schema command is reused. The final seven-command G2 lane refreshed all affected + implementation/consumer checks; the failed aggregate is never called passed. +* Harness mutation checks passed 17 cases; Testing event parser checks passed 19. + Exact module/function/parameter discovery and all 49 baseline metric probes + remain required in the full lanes; a focused lane cannot substitute for them. + +Raw commands, exits, environment, input SHA-256 lists, Swift Testing event streams +and log hashes are materialized under the private G2 evidence directory. Source +receipts record G1 HEAD 8de39d5 plus the actual working-tree hashes at execution; +that Git SHA alone does not contain G2. The return's SourceEvidenceMap verifies +complete path/hash sets against the committed final source and separately verifies +unchanged-core suite reuse. The final clean-head `Scripts/ci.py --lane docs` +receipt records actual DocC/comment/consumer/site outcomes, rather than inferring +success from authored prose. Build outputs/private logs remain outside source Git. + +## Meaningful behavior and resolved findings + +The 21 added functions exercise the 16 reference draws, 640 bounded selections, +a forced rejected draw (two draws, 61 work units, 34 trace bytes), terminal quota +failure, quantum-one cancellation inside a draw, normalized exact large/fractional +numbers and scalar-distinct keys, 100 nested arrays, numeric/depth admission, +corpus admission/order/immutability, independent transport/trace goldens, all 49 +caps and all policy flags, exact trace capacity, circular scans including false +verdicts, stable keyword/reference traversal and constructor cancellation. +The strengthened sharing function covers both minimal and seeded demonstration. +Native certification against the original compiled root remains the sole success +path; no validation report constructor or alternate validator was introduced. + +Compiler probes exposed implicit Sendable inference on internal noncopyable value +types. Explicit unavailable conformances now opt mutable helpers out; no unsafe +annotation or compiler compatibility flag was added. A throwing noncopyable member +initializer was replaced with local construction followed by a final move. + +Exploratory failures remain retained: an incorrect fixed-decimal expected byte +string was corrected to the existing writer's normalized coefficient/exponent +spelling; mathematical exactness assertions remain. A corpus test initially hit +the packaged meta-schema string cap first. Its positive control exposed that gap; +the corrected test admits a null corpus at 4096 bytes and rejects a 4097-byte +corpus token. Production/default limits were not changed. The consumer marker +was updated to the actual 15 cases and stale/missing markers now fail mutation +checks. An unavailable Python cache and misspelled parser-probe command were +replaced with cache-free syntax checking and the actual 19-case verifier. + +## Remaining boundaries + +This is a native G2 milestone, not full schema generation, a satisfiability solver +or a release candidate. G3/G4 constructive fixtures, G5 complete lifecycle review, +G6 final documentation and G7 platform qualification remain separate. No new +Linux/WASI/mobile, controlled performance or RSS qualification is asserted. +Trace bytes never silently truncate; failure/cancellation returns no certificate. +Logical retained weights and owned work do not cap allocator RSS or caller code. + +Main independently advanced to 037e735 with 1.5.1 repairs while this isolated train +retained its approved G0/G1 base. That includes a schema best-effort diagnostic +repair and restored tests/harness checks. Integrate and reconcile that source and +its new cardinalities before G7/G8; this G2 result does not qualify that merge. +The separate main/sibling checkouts were not edited. No push, PR, merge, tag, +release, deployment or external message was performed. diff --git a/Documentation/Architecture/Release1.6/G3Construction.md b/Documentation/Architecture/Release1.6/G3Construction.md new file mode 100644 index 0000000..1733549 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G3Construction.md @@ -0,0 +1,61 @@ +# G3 conservative typed construction + +G3 extends the isolated G2 source at 42a523c. Native JSONSchema compilation and +complete root validation remain the only dialect/success authorities. No core +JSON or evaluator semantic changes, online resolution or dependencies are added. + +After declared candidates are tried, a finite typed search constructs candidates +from compiler-qualified nodes. Positive allOf and static reference constraints +are gathered as conjunctions, not merged JSON schemas. AnyOf/oneOf child hints are +separate alternatives and every proposal is validated against the original root. +Dynamic construction and same-instance reference cycles remain explicit failures. +Nested instance construction uses an explicit frame stack; no recursive Swift +constructor or nested helper meter is introduced. + +Type selection uses a fixed order: null, boolean, number (integer when required), +string, array, object. A declared number accepts integral and fractional values; +an integer restriction retains only mathematically integral candidates. Keyword +and member identity uses exact decoded scalar UTF-8. Required keys are the union +of active conjuncts, sorted exactly. Every applicable property/additional schema +contributes constraints for that member; no last-wins dictionary or inferred +requiredness is used. Optional false properties can be omitted. MinProperties +can add declared or synthetic names under each applicable additional schema. +Arrays honor basic sizes and prefix/items declarations. Unique items vary leaf +choices, with uniqueness certified only by the full validator. + +Exact numeric proposals use bounded signed decimal digit arithmetic. A lower +bound is rounded to an exact positive step (one for integers; a decimal step +derived from bound precision for numbers). MultipleOf supplies an exact step; +integer multiples remove only the denominator's factors of two/five. Open bounds +advance by that exact step, never an arbitrary epsilon. Dense alignment beyond +numberDigits fails before allocation. Upper-bound proposals are used for negative +ranges. This is conservative search, not a general numerical constraint solver. + +Strings count Unicode scalars. Minimal uses the shortest available filler; seeded +demonstration can use admitted synthetic tokens. Optional asserted email uses a +fixed example.com token. Format metadata never enables assertions. Arbitrary regex +inversion, dynamic scope, difficult conditionals/dependencies and evaluated-location +construction remain G4 or explicitly incomplete. Defaults/examples remain hints +and can be invalid; no value is repaired or schema rewritten. + +All staging, key comparisons, decimal digits, queue/stack growth, reference steps, +trace bytes and final native checks use one noncopyable invocation meter. Each +candidate has checked value capacities; work and lifetime totals never reset. +Discarded staging refunds only its live logical ownership, retaining trace bytes +and cumulative work. Frame/value/slot weights use JSONLogicalWeights v1; qualified +constraint views reserve 64 logical bytes per slot. Bytes are not RSS. Caller +fixtures and ordinary Swift Testing oracle calls are separate qualification work. + +The candidate algorithm becomes s3-candidates-v3. Snapshot/corpus/policy v1 byte +transports remain unchanged. Trace becomes swift-json-example-trace-v2 with header +SwiftJSON-example-trace-v2 LF, the same envelope/events as G2, plus offer role 5 +(typed proposal), and code 6(depth,variation) for a typed value request. G2 vectors +remain preserved in history/archives; G3 vectors bind the new algorithm/version. +No cross-version or unexecuted cross-platform identity is promised. + +Qualification must execute all 24 G3 must-generate IDs, both existing profiles and +multiple deterministic seeds; separately check two sound trivial proofs. Every +success must also pass an independent public native validation and strict value +roundtrip. The complete amended 44-case fixture is a pinned input, not a claim +that G4's remaining cases are implemented. A machine-readable capability ledger +keeps implemented hints, conservative search and deferred features distinct. diff --git a/Documentation/Architecture/Release1.6/G3Qualification.md b/Documentation/Architecture/Release1.6/G3Qualification.md new file mode 100644 index 0000000..79793c4 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G3Qualification.md @@ -0,0 +1,112 @@ +# G3 native construction milestone + +G3 extends G2 commit `42a523c6d0a4dab9195c847feb4e47b43803bfd1` on the +isolated `codex/1.6-schema-examples` branch. The accepted construction and transport +decisions are in [G3Construction](G3Construction.md); the machine-readable +[capabilities](Capabilities.json) distinguish proposal hints from native semantic +authority. No existing JSON/JSONSchema production source or dependency changed. +G0-G2 history remains intact. G4-G8 and main's independent 1.5.1 integration remain +open; this is not a release candidate. + +## Actual commands and outcomes + +On macOS 27.0 arm64, Xcode 27.2 (27B5019j), Apple Swift 6.4 +(`swiftlang-6.4.0.34.1 clang-2100.3.34.1`), SDK 27.2, language mode 6: + +* `python3 -B Scripts/ci.py --lane schema-examples-construction --output Artifacts/SchemaExamplesS3/G3/native-final` + exited 0, all eight commands passed. Exact eight-product isolation and empty + dependencies passed. All 61 Swift Testing functions passed in Debug and Release. +* `python3 -B Scripts/ci.py --lane schema-examples-safety --output Artifacts/SchemaExamplesS3/G3/safety-final` + exited 0, all three commands passed: 61 Release ASan functions, two TSan sharing + functions with 32 concurrent invocations each, and strict-memory-safety + compilation with warnings as errors. No unsafe annotation or compatibility + flag was introduced. +* The actual public consumer independently compiled plans, certified/validated + and strictly roundtripped all 24 required cases, two profiles and four seeds + (192 invocations per process), in two separate processes. Exact values, traces + and cumulative statistics matched. Each process checked 90 fixture witness + expectations and two separate trivial proofs. These are repeated executions, + not 384 distinct fixtures. The other 18 G4 cases are not counted as successes. +* The preserved default public consumer passed 15 cases twice and two expected + public access-control rejections. The independent byte-format/SHA-256 oracle + matched both profiles. Implementation-only compiler fixtures accepted one move + and immutable-facade sharing, and rejected eight copy/Sendable violations with + the expected ownership diagnostics, not missing imports. +* Harness mutation checks passed 22 cases; the Testing event parser passed 19. + Exact full-lane discovery remains 480 native functions, 325 parameter + invocations, 17 sharing functions and all 49 baseline metrics. Focused G3 + results do not substitute for those later release lanes. + +Commands use `DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer` and +the selected Xcode's macOS 27.2 SDK. Receipts retain the actual command arrays, +environment, exits, log/event hashes and complete working-tree source hashes. +Their recorded Git HEAD is the G2 base plus those captured G3 files; that SHA +alone does not contain this implementation. The private final SourceEvidenceMap +compares full critical path sets/hashes to the committed source. The final +clean-head `Scripts/ci.py --lane docs` receipt records actual documentation, +compiled-consumer and site results for that final snapshot. + +## Regression evidence and fixture identity + +The amended fixture bytes are pinned to +`5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3`. +The unchanged original is separately retained at +`6f2bfd4fdb7c7e206b6570c2156ca8e248831840733b0258aa8cd5938b35649f`. +The [manifest](../../../Tests/Fixtures/SchemaExamples/Manifest.json) preserves +G0's integral-number amendment and the complete 44-case/155-witness inventory. +The G3 subset has 90 witness expectations; no old negative was removed to qualify +the generator. These authored fixtures are not independent external-suite passes. + +A 654-file path-set/hash comparison proves the existing JSON/JSONSchema/regex +source, 69 schema functions, their pinned fixtures and qualification policies +unchanged from G2's separately passed schema command: 12 parameter invocations, +2300 evaluated pinned cases and 37 separate policy exclusions. Its aggregate G2 +lane failed a stale consumer marker and is never relabelled passed. The reuse +record explicitly excludes the new G3 fixture directory, which that old suite +does not read. Raw selected-command logs/events and the original receipt accompany +the G3 return; no fresh whole-package or platform-suite pass is inferred. + +## Behavior and review findings + +The 19 added functions cover the fixture matrix, sound proofs, conjunctive exact +NFC/NFD keys and additionalProperties scope, huge/tiny/open signed decimals, +scalar lengths, native format policy, min-properties/unique arrays, nested +arrays, exact cumulative work boundaries after a false verdict, finite staging +caps, dense-alignment rejection, terminal operational failure/cancellation, +100 repeated calls, concurrent sharing, twelve positional prefix indices, +2940 independent integer rounding checks and repeated static-target DAGs. + +Review found that normalized JSON number spelling cannot represent positional +indices: index 10 needs `"10"`, not `"1e1"`. The constructor now uses prepaid, +bounded positional UInt64 formatting. Repeated static references are deduplicated +as DAG sharing; only an active ancestor cycle is rejected. Internal frame types +explicitly opt out of Sendable inference; compiler fixtures enforce that boundary. + +The first exploratory run's old format-hint test expected search exhaustion. +Typed construction correctly found null: format alone does not imply string. +The test now separately verifies the invalid string against native assertion and +uses one attempt to exercise the rejected hint. Another old invalid-default test +now expects a later independently constructed valid object while proving the +original default remains unchanged. The corrected 60-function exploratory run +and final 61-function lanes passed; failed attempts remain in private evidence. + +## Remaining limits + +The candidate algorithm is `s3-candidates-v3`; complete trace is +`swift-json-example-trace-v2`. Source/corpus/policy v1 transports remain unchanged; +G2 vectors remain historical. No cross-version/platform identity is implied. +Every returned value still requires completed validation of the original root. +No schema merge, branch-local certificate, alternate validator, approximate +number, online resolver or unconstrained retry is introduced. + +Construction is conservative: arbitrary regex inversion, dynamic scope, difficult +dependencies/conditionals and general satisfiability are not solved. Dense decimal +alignment can exhaust a finite quota. All owned staging and every native false +verdict share the generation ledger; preparation has its separately reported +compilation ledger. Scratch retention is conservatively charged. Logical bytes +are not allocator RSS and caller qualification work is not an instruction quota. +G5's broader lifecycle review, G6 final documentation and G7/G8 integration, +Linux/WASI/mobile/performance/release qualification remain separate. + +No sibling source, main checkout, remote branch, PR, tag, release, deployment or +external message was changed by this local milestone. diff --git a/Documentation/Architecture/Release1.6/G4Construction.md b/Documentation/Architecture/Release1.6/G4Construction.md new file mode 100644 index 0000000..733213b --- /dev/null +++ b/Documentation/Architecture/Release1.6/G4Construction.md @@ -0,0 +1,46 @@ +# G4 conservative advanced proposals + +G4 extends G3 commit 3951b99 on the isolated 1.6 branch. The original compiler, +qualified offline plan and complete root validator retain exclusive semantic +authority. No schema evaluator, public API, dependency or platform is added. + +The ten additional must-generate fixtures and eight may-fail fixtures are distinct +acceptance groups. Every successful proposal must pass full native validation and +strict roundtrip. A may-fail fixture may also succeed; unsupported/exhausted is +incomplete search, never a proof of unsatisfiability. Operational errors and +cancellation terminate the invocation and cannot be counted as incomplete search. + +Two narrow extensions are selected: + +* Array length includes each active contains declaration's minimum (default one). + Only when contains applies are minContains/maxContains consulted. The first N + items receive that declaration's qualified contains child in conjunction with + their own prefix/items constraints. Several contains declarations remain separate + conjuncts. The native validator alone counts actual matches, checks maxima and + propagates evaluated-location annotations. Avoiding extra matches or finding + disjoint matching partitions remains incomplete; no independent matcher runs. +* A private proposal-only scanner handles fully anchored ASCII sequences: printable + literals, escaped regex punctuation, a single printable ASCII range per character + class, and exact {n} repetition. Variation chooses within each range. It does + not match strings, invert arbitrary regex, accept malformed schemas or replace + native regex. Unsupported syntax falls back to the existing finite token/filler + search and still requires native certification. Output bounds are checked before + append; source scans, repeat counts, output copies and rejection paths share the + invocation ledger. No Foundation regex or private runtime dependency is added. + +Existing finite alternatives handle the conditional fixture without interpreting +if/then/else: the rejected adult/age-zero object consumes work, then a later child +proposal can pass. Optional dependent/pattern/name declarations may be satisfied +by an empty object; that demonstrates fixture coverage, not general constructive +support. Unevaluated-location semantics are certified by native annotations, not +reimplemented. Static references use compiler-qualified identities, including +explicit external registry resources and anchor/base transitions. Dynamic targets +are not guessed; reaching a dynamic-scope edge remains unsupported. Omitting an +optional recursive/dynamic child may produce a valid finite witness. + +Algorithm becomes s3-candidates-v4. The trace v2 grammar is unchanged: existing +typed-request/offer events suffice, and the trace envelope binds the new algorithm. +Snapshot/corpus/policy v1 and SplitMix64 v1 remain unchanged. G3 vectors and archives +remain immutable historical evidence; no cross-version/platform identity is claimed. +All new helpers reuse the one noncopyable generation meter. G5's broader lifecycle +review and G6-G8 integration/platform/release gates remain separate. diff --git a/Documentation/Architecture/Release1.6/G4Qualification.md b/Documentation/Architecture/Release1.6/G4Qualification.md new file mode 100644 index 0000000..1821f41 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G4Qualification.md @@ -0,0 +1,100 @@ +# G4 native qualification + +G4 extends G3 commit 3951b99bb0f690098cf8bef61ce872119e4835da on the +isolated `codex/1.6-schema-examples` branch. See the accepted +[G4 proposal boundary](G4Construction.md), [capabilities](Capabilities.json) and +[progress](Progress.json). G0-G3 history is preserved. G5-G8 and main's independent +repair integration remain open; this is not a release candidate. + +## Actual outcomes + +On macOS 27.0 arm64, Xcode 27.2 (27B5019j), Apple Swift 6.4 +(swiftlang-6.4.0.34.1 clang-2100.3.34.1), selected SDK 27.2, language mode 6: + +* `python3 -B Scripts/ci.py --lane schema-examples-advanced --output Artifacts/SchemaExamplesS3/G4/native-final` + exited 0; all nine commands passed. Exact eight-product isolation and empty + dependencies passed, with 79 Swift Testing functions each in Debug and Release. +* `python3 -B Scripts/ci.py --lane schema-examples-safety --output Artifacts/SchemaExamplesS3/G4/safety-final` + exited 0; all three commands passed: 79 Release ASan functions, three TSan + sharing functions with 32 concurrent invocations each, and strict-memory-safety + compilation with warnings as errors. No unsafe conformance or compatibility + flag was added. +* The actual public G4 consumer passed all ten additional required cases across + two profiles/four seeds: 80 mandatory successes per process. It also exercised + all eight may-fail cases: 32 certified calls from four cases, and 32 explicit + incomplete-search calls from four cases, totaling 144 calls per process. + Two separate processes matched exact values, outcomes, digests and successful + accounting. Every success passed independent public native validation and a + strict roundtrip. No value or successful work report was invented after error. +* All 44 schemas/155 fixture witness expectations received native verdict checks, + including six negatives attached to the G3 trivial-proof cases. The preserved + G3 consumer additionally passed 192 required combinations and two proofs in + each process. The default consumer passed 15 cases twice and two public compiler + negatives. Independent stdlib byte-format/SHA-256 vectors matched both profiles. + Trusted implementation compiler probes accepted one positive and rejected eight + copy/Sendable violations for the expected diagnostics, not missing imports. +* Harness acceptance/rejection mutations passed 26 cases; Testing event parser + regressions passed 19. The capability ledger classifies all 59 compiled keyword + cases exactly once, with referenced behavior functions verified to exist. + Full release lanes still require 498 functions, 325 parameter invocations, + 18 sharing functions and all 49 baseline metric probes. Focused runs do not + substitute for those later full release gates. + +Commands explicitly selected Xcode and its SDK through DEVELOPER_DIR/SDKROOT; +there was no global toolchain switch. Native/safety receipts record G3 HEAD plus +captured G4 working-tree hashes. The final SourceEvidenceMap verifies complete +critical file sets/hashes against committed source. Only maintainer architecture/ +progress prose can differ. The final clean-head docs receipt records actual DocC, +consumer and static-site outcomes for the final snapshot. Private receipts retain +all actual command arrays, exits, toolchain, source inputs and log/event hashes. +No controlled-performance or RSS measurement is claimed on this shared host. + +## Honest outcomes and semantic authority + +The may-fail cases oneof-overlap, recursive-tree, dynamic-ref and +external-offline-ref produced native-certified witnesses. The dynamic case omits +an optional child; constructive dynamic navigation is not qualified. The +conflicting-allof and not-string cases returned candidateConstruction unsupported; +unfulfillable-unique-enum and oneof-overlap-limited exhausted 16 attempts. The latter +has valid fixture witnesses: exhaustion is explicitly not an unsatisfiability +proof. No operational error/cancellation can count as accepted incompleteness. + +The new contains hints choose minimum matching positions beside each conjunct's +prefix/items constraints. Native validation alone establishes all matches/maxima +and evaluated locations. The anchored ASCII scanner proposes printable literals, +escaped punctuation, single range classes and exact repeats. It never matches +strings or replaces native regex. General inversion, disjoint contains partitions, +triggered dependency/key synthesis, dynamic scope and satisfiability remain incomplete. +Optional omission satisfying a fixture does not imply broader constructive support. + +The 18 added tests cover the full G4 fixture family, ten unsupported grammar forms, +Unicode corpus identity, pattern conjunctions, contains defaults/zero/inactive +siblings, prefix/unique/unevaluated composition, surplus-match failure, cardinality +and scanner work caps, malformed/native regex failures, cancellation latching, +conditional false-verdict work, qualified external anchors/base transitions, +missing resources, reached dynamic scope, recursive depth bounds and concurrency. +The conditional test proves the exact sum of rejected and accepted native evaluation +work, success at that cumulative ceiling and failure one unit below it. +All helpers reuse the one generation ledger; no nested meter or work reset is added. + +Algorithm becomes s3-candidates-v4; trace grammar stays swift-json-example-trace-v2 +and binds the new algorithm. Source/corpus/policy v1 and SplitMix64 v1 are unchanged. +Historical G3 vectors/archives are retained. The original/amended fixture bytes +and all classifications are unchanged; the manifest adds explicit G4 ID groups. +The amended SHA-256 remains +5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3. + +## Regression and remaining gates + +A 654-file equality proof supports reuse of G2's separately passed schema command: +69 functions, 12 parameter invocations, 2300 pinned evaluated cases and 37 separate +policy exclusions. That old aggregate failed a stale consumer marker and is never +relabeled passed. New S3 fixtures are separately qualified and are not read by +those existing tests. Raw selected-command receipt/log/event bytes accompany the +G4 return. JSON/JSONSchema/regex production source and Package.swift are unchanged. +No fresh external-oracle, Linux/WASI/mobile, full-package or performance pass is +inferred. Logical retention is not RSS; caller work is not an instruction quota. + +Main's independent 1.5.1 repair source and discovery counts must be reconciled +before final release qualification. No sibling source, remote branch, PR, merge, +tag, release, deployment or external message was changed in this milestone. diff --git a/Documentation/Architecture/Release1.6/G5Qualification.md b/Documentation/Architecture/Release1.6/G5Qualification.md new file mode 100644 index 0000000..b505489 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G5Qualification.md @@ -0,0 +1,78 @@ +# G5 native qualification + +G5 extends G4 commit 304f900489dc6c24bce862eccf6adc9865df1027 on the isolated +`codex/1.6-schema-examples` branch. G0-G4 history/archives are preserved. See +[G5 resources and lifecycle](G5Resources.md) and [progress](Progress.json). +G6-G8 and main's independent repair integration remain open. + +## Actual native outcomes + +The supported host was macOS 27.0 arm64, Xcode 27.2 (27B5019j), Apple Swift 6.4 +(swiftlang-6.4.0.34.1 clang-2100.3.34.1), SDK 27.2, language mode 6. +Every command explicitly selected DEVELOPER_DIR/SDKROOT; no global selection changed. + +* `python3 -B Scripts/ci.py --lane schema-examples-advanced --output Artifacts/SchemaExamplesS3/G5/native-qualified` + exited 0, all nine commands passed. Exact manifest/product isolation and empty + dependencies passed. All 96 generator Swift Testing functions passed in Debug + and Release, including 17 added G5 functions. +* `python3 -B Scripts/ci.py --lane schema-examples-safety --output Artifacts/SchemaExamplesS3/G5/safety-qualified` + exited 0, all three commands passed: 96 Release ASan functions, four TSan sharing + functions (32 invocations each, including mixed success/cancellation), and + strict-memory-safety compilation with warnings as errors. +* Public consumers again passed 15 cases/two processes, two access-control compiler + negatives, one trusted ownership positive/eight compiler negatives, and independent + byte-transport/SHA vectors. G3 again passed 192 required calls plus two proofs per + process; G4 passed 144 calls per process (112 certified, 32 explicit incomplete). + All 44 schemas/155 witness expectations received native checks. Two processes + matched outcomes, exact values, digests and successful accounting. Incomplete + search is not a validity verdict or an unsatisfiability proof. +* Harness mutations passed 26 acceptance/rejection cases and the event parser passed + 19 regressions. Exact full-lane requirements become 515 functions, 325 parameter + invocations and 19 sharing functions; all 49 baseline metric probes remain. + These focused runs do not claim execution of later full release/platform gates. + +The final clean-head docs receipt records seven native DocC catalogs, authored API +coverage, README examples, actual public consumer and static-site validation for the +committed snapshot. SourceEvidenceMap verifies full critical file-set/hash equality +for native/safety and exact clean-head equality for DocC. Private materialized +receipts retain commands, exits, toolchain, source inputs, logs and event hashes. +No controlled timing, throughput or RSS result is claimed on this shared host. + +## Review findings and boundaries + +Removed a real unmatched 32-byte refund from trace finalization. A new direct +reservation test proves the transport leaves precisely its 64-byte SHA hex result; +a separate policy transport test proves its real temporary array slots are released. +Candidate values/order, algorithm v4 and trace v2 bytes are unchanged. G4 comparison +receipts verify exact outcomes, traces and work; logical peaks may increase by 32. +No cap, logical weight, public signature, dependency, core/schema or regex source +was changed. Some staging reservations deliberately remain conservative until the +proposal or invocation ends; no exact heap/liveness or RSS guarantee is asserted. + +The actual invocation boundary now latches every terminal error and releases its +remaining logical reservations on success/transfer and failure. Tests preserve a +caller's earlier reservation, repeatedly reuse a borrowed successful ledger without +resetting work, use results after source/registry scope exit, prove false-verdict +work/diagnostics and exact/one-short quotas for seven metrics, and check zero work, +preparation quota, overflow, nested cycles and exhausted branches. Cancellation is +triggered at charged checkpoints in discovery/construction/native regex, long decimal +division and final trace hashing; cleanup cannot clear terminal health or publish a +partial certificate. The prepared immutable generator remains independently reusable. + +Exploratory failures are preserved separately: an initial missing try in a new +assertion, a comparison using a different number representation, and a TSan compiler +rejection of a loop task capture. Final tests use identical parsed exact candidates +and explicit checked Sendable value captures. Diff review restored a legitimate +policy-buffer release accidentally removed alongside the trace refund, and added its +own regression case. The superseded native Release job was deliberately stopped; +its partial receipt is never relabeled a pass. No failure was skipped or silenced. + +A complete unchanged 654-file scope reuses G2's separately passed schema command: +69 functions, 12 parameter invocations, 2300 pinned evaluated cases and 37 separately +classified policy exclusions. The old aggregate failed a stale consumer marker; +that status is preserved. Reused logs/events/source are materialized in the return. +All original/amended fixture bytes and classifications remain unchanged. + +G6 final public documentation, G7 full/platform qualification and G8 release review +remain open. Reconcile main's 1.5.1 repairs and counts before those release gates. +No sibling source, remote branch, PR, merge, tag, release or deployment was changed. diff --git a/Documentation/Architecture/Release1.6/G5Resources.md b/Documentation/Architecture/Release1.6/G5Resources.md new file mode 100644 index 0000000..0b1b49a --- /dev/null +++ b/Documentation/Architecture/Release1.6/G5Resources.md @@ -0,0 +1,82 @@ +# G5 cumulative accounting and lifecycle + +G5 retains `s3-candidates-v4`, SplitMix64 v1 and all existing byte transports. +Candidate order, values, trace bytes and semantic authority are unchanged. The +accounting correction removes an unmatched 32-byte refund during trace hashing. +Successful logical retention may therefore increase by 32 bytes relative to G4; +a previously marginal retention cap may now fail correctly. Historical receipts +and goldens remain available. This changes neither the 49 default caps nor the +version 1 logical weights. + +## Invocation boundary + +Public `generate(seed:profile:)` creates one noncopyable JSONWorkMeter with the +intersection of generation and validation limits. Its internal borrowed-meter +entry is also used by behavior tests. That entry requires the identical policy +and core domain; it is not a public accumulator API. Discovery, reference and +branch expansion, typed staging, exact writer/parser materialization, every native +validation and trace finalization borrow that same ledger. There is no nested +meter and no replacement of a failed ledger. Preparation separately shares one +compiler-domain ledger across compilation and source/corpus/policy hashing. +Caller construction of documents, registries and corpora precedes these operations. + +Completed false verdicts consume evaluation/numeric/regex work, diagnostic bytes +and issue counts. Only their discarded live ownership is released. No failure +report is fabricated from a throwing validator. Operational errors, cancellation, +search exhaustion, unsupported construction and trivial proofs leave the search +terminal. The boundary latches even errors that originate outside the meter +(such as nonproductive native reference evaluation). Catching them in trusted +internal code cannot resume that invocation. A new public call has independent +health; the immutable prepared generator remains reusable and Sendable. + +Work, numeric work, evaluation work, aggregate regex work, issue counts and +trace/diagnostic charges are cumulative. Per-value input/output/node/text ceilings +are retired between materializations; retirement preserves lifetime report totals +and never refunds work. Per-search regex work resets only its per-search ceiling; +aggregate regex work remains cumulative over all candidates. Active depth, frame, +container cardinality and individual token capacities keep their existing meanings. +They are not cumulative generated-node or total-depth counters. There is no new +unlimited setting and no arbitrary-schema completeness claim. + +## Ownership and reports + +Search uses a conservative invocation arena: some construction scratch reservations +are held until the containing proposal/invocation ends even when Swift can release +an individual temporary earlier. Rejected proposal and verdict ownership is +released at their existing boundaries. Complete trace bytes remain charged until +hashing; SHA-256 scratch and the 64-byte digest overlap are checked. No trace +truncation or retained error-history array is introduced. + +A successful report snapshots counts at certification, including the returned +value/verdict, receipt metadata and any still-reserved invocation staging. Before +the borrowed entry returns, it releases its remaining logical reservations as the +result transfers to the caller. On failure it releases all invocation reservations +without changing charged work, diagnostics, retired admission or terminal health. +The caller's preexisting live reservations are preserved. The report's retained +count is a conservative certification snapshot, not a post-return heap measurement. +Peak logical bytes are neither allocated bytes nor process RSS. Callback execution +in the package-only checkpoint test observer is trusted test work, not an owned +instruction quota or a public generator callback. + +No result retains a generator, plan handle, mutable trace or search stack. Its +JSON value, native verdict and policy/identity values remain usable after the +preparation/source/registry scope ends. Immutable preparation retains its admitted +source/registry/corpus/plan until the caller releases it. There is no global mutable +cache. Native sanitizers and concurrent failure/success tests qualify this code; +there is no allocator-independent proof of immediate physical memory reclamation. + +## Qualification boundary + +`LifecycleAndAccountingTests.swift` adds real-engine cases for exact successful +quota/one-short boundaries (seven work/diagnostic/retention metrics), trace ownership, +repeated borrowed calls, source-scope result lifetime, multiple false verdicts, +zero work, exact preparation quota, cyclic construction, exhausted branches, +native evaluation errors, checked overflow and deterministic cancellation. +Cancellation uses actual charged checkpoints rather than sleeps: decimal division, +discovery/construction/native regex and final trace hashing. Every interrupted +search checks terminal latching and reservation cleanup. Separate concurrent +invocations mix cancellation and success without poisoning preparation. + +The focused native/safety/docs receipts qualify this increment only. Full release +G7/G8, main repair integration, Linux/WASI/mobile and controlled performance remain +separate. See G5Qualification.md for observed commands/counts, not inferred passes. diff --git a/Documentation/Architecture/Release1.6/G6Documentation.md b/Documentation/Architecture/Release1.6/G6Documentation.md new file mode 100644 index 0000000..9923b3d --- /dev/null +++ b/Documentation/Architecture/Release1.6/G6Documentation.md @@ -0,0 +1,65 @@ +# G6 public documentation and example contract + +G6 extends G5 commit 30cf344c674a7514db2a578397ade642c0ab942e on the isolated +S3 branch. [Current API](API.md) supersedes provisional G1 interface prose while +preserving G0-G5 history. Runtime behavior, defaults, all 49 caps, eight root +products, private targets, compiler baseline and empty dependencies are unchanged. +The three production Swift files touched in G6 change documentation comments only. + +## Executable examples + +Every Swift block in the five JSONSchemaExamples DocC guides is extracted in +stable filename/block order into the separate public DocumentationExamples.swift. +The generated function runs each block in its own scope with public JSON, +JSONSchema and JSONSchemaExamples imports. Source-location directives point compiler +errors at the authored guide. Synchronization is exact; omissions, additions, +empty blocks, foreign imports and changed bodies fail the checked ten-block contract. + +| Guide | Blocks | Observable assertions | +| --- | ---: | --- | +| VerifiedCandidates | 3 | Exact large integer/native verdict and work; offline definition selection and independent validation/roundtrip; honest exhausted search | +| TypedConstruction | 2 | Exact lower bound; scalar-distinct required keys, present null and omitted optional default | +| AdvancedConstruction | 1 | Contains/items/pattern/uniqueness candidate receives complete native root verdict | +| DeterministicConstruction | 2 | Custom admitted corpus/profile identity; same prepared inputs/seed preserve exact value, trace and work | +| PoliciesAndLimits | 2 | Invalid enum candidate charges work/attempts and checked remaining total; format annotation/default metadata remain unchanged while explicit assertion rejects | + +The existing public qualifier executes this mode in two processes, in addition +to the preserved 15 cases, two compiler negatives, separate process vectors and +independent transport oracle. It checks exact completion markers, never a blanket +nonzero count. Eleven harness mutations exercise source and receipt acceptance/ +rejection and verify all 49 metric classifications. These are harness checks; +Swift behavior remains covered by native Swift Testing and real public consumers. + +The root README adds one exact native-certified schema example, synchronized with +the general Consumer's fourteen executable fences. That consumer explicitly adds +the optional seventh library; no JSON or JSONSchema target gains a dependency. +The separate schema consumer remains JSON/JSONSchema/JSONSchemaExamples only. + +## Scope and accuracy + +Guides, inline comments, README, documentation index, tooling and current API/limits +contracts describe the actual v4 constructor. They distinguish conservative hints +from validator support, explicit incomplete search from proofs and operational +errors, separate preparation from cumulative generation, and explain certification +retention versus heap/RSS. They do not imply a released 1.6 product, cross-platform +release qualification or arbitrary-schema completeness. Historical G1-G5 records +retain their original observations and claims. + +All five guide routes, including PoliciesAndLimits, are required in the native +DocC static-site build. DocC warnings/symbol-link errors remain failures. All 60 +S3 public declarations retain authored comments and named-parameter coverage; +compiler-derived API identity is compared with G5 before closure. + +JSONSchemaDSL is absent from this branch's inspected Package.swift and its main +base. The independent train is untouched; no shim or speculative dependency is +added to demonstrate nonexistent integration. The conditional DSL/manual equality +example is deferred until an available integrated product is reconciled for G7/G8. +No format assertion, compiled-plan behavior or registry default is changed. + +## Qualification and next gate + +Run affected native and documentation lanes through Scripts/ci.py. Preserve exact +96-generator/515-root function counts, 325 parameter invocations, 19 sharing functions, +all 49 metric probes and independent fixture policies. Evidence records commands, +exits, source hashes and selected native tools. G7/G8 full/platform/release gates +and main's independent repairs remain separate; no remote action is implied. diff --git a/Documentation/Architecture/Release1.6/G6Qualification.md b/Documentation/Architecture/Release1.6/G6Qualification.md new file mode 100644 index 0000000..d4c7569 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G6Qualification.md @@ -0,0 +1,78 @@ +# G6 native documentation qualification + +G6 extends G5 commit 30cf344c674a7514db2a578397ade642c0ab942e on the isolated +`codex/1.6-schema-examples` branch. G0-G5 history/archives remain intact. The +[current API](API.md), [all 49 metric scopes](SchemaExamplesLimits.md) and +[documentation contract](G6Documentation.md) govern the current descriptions. +This closes the native G6 increment, not G7/G8 or a 1.6 release candidate. + +## Native outcomes + +macOS 27.0 arm64, Xcode 27.2 (27B5019j), Apple Swift 6.4 +(swiftlang-6.4.0.34.1 clang-2100.3.34.1), SDK 27.2, language mode 6. +Commands explicitly select DEVELOPER_DIR/SDKROOT; no global toolchain changes. + +* `python3 -B Scripts/ci.py --lane schema-examples-advanced --output Artifacts/SchemaExamplesS3/G6/native-final` + exited 0; all nine commands passed, including 96 Debug and 96 Release generator + Swift Testing functions, exact eight-product/empty-dependency checks, one ownership + positive/eight negatives and preserved public/fixture consumers. +* `python3 -B Scripts/ci.py --lane schema-examples-safety --output Artifacts/SchemaExamplesS3/G6/safety-final` + exited 0; all three commands passed: 96 Release ASan functions, four TSan sharing + functions, strict-memory-safety compilation with warnings as errors. +* Every Swift block in all five S3 guides is synchronized with its separate public + consumer: ten snippets execute in two processes, retaining all 15 original cases, + two public compiler negatives and independent process/transport vectors. The new + README example compiles in the general seven-library consumer with fourteen fences. +* G3 repeated 192 required certifications/two proofs per process; G4 repeated 144 + calls per process (112 certified, 32 explicit incomplete). All 44 schemas/155 native + witness expectations were checked. Policy exclusions, incomplete search and + operational errors are not successes. No failure value/report was fabricated. +* Documentation harness passed eleven mutations and exact 49-metric classification; + CI harness passed 26 mutations, and the event parser passed 19 regressions. Exact + 96-generator/515-root/325-parameter/19-sharing and all 49 cap requirements remain. + +The clean-head docs receipt records actual native symbol graphs, all seven DocC +catalogs with warnings as errors, authored declaration/parameter coverage, compiled +README and public guide consumers, and static-site routes for all five S3 guides. +SourceEvidenceMap binds native/safety critical hashes and exact committed DocC inputs. +Private materialized receipts retain commands, exits, source hashes and log/events. + +## API, defaults and source compatibility + +All 181 production Swift files match G5 after removing only documentation-comment +lines; only Corpus, Generator and Options comment files differ in exact bytes. Root +Package.swift and every existing test/fixture match G5 byte-for-byte. The native +public compiler graph comparison verifies all 60 authored S3 declarations/signatures +unchanged. The original compiled plan, registry, format/default behavior and v4 +algorithm/transport IDs remain authoritative. No product/runtime dependency is added; +only the independent general consumer opts into the existing examples product. + +Guides execute exact numbers/scalar keys, optional/default/null distinctions, +offline definition selection, original-root validation and roundtrip, conservative +contains/pattern proposals, seeded replay, remaining work, policy preservation and +honest exhaustion. All new descriptions distinguish constructor hints from native +validator support and logical retention from heap/RSS/caller instruction accounting. +The README clearly labels the API as unreleased 1.6 development. + +JSONSchemaDSL is absent from the inspected S3 manifest and main base. The conditional +DSL/manual equality example is deferred until its product is available during later +integration. No shim, speculative import or sibling edit was introduced. + +## Preserved evidence and remaining work + +An unchanged 654-file core/schema/fixture/policy scope supports reuse of G2's separately +passed 69-function/12-parameter/2300-evaluated-case schema command, with 37 separate +policy exclusions. Its old aggregate failed a stale consumer marker; that status is +preserved. Raw reused command/log/event/source bytes accompany the review return. +No fresh independent oracle, full-package, Linux/WASI/mobile or performance pass is +inferred. G7/G8 and main's independent 1.5.1 repair integration remain open. + +An exploratory edit helper had a Python syntax error before applying its patch; the +corrected structured patch was then qualified. An initial harness parser counted a +Markdown divider as a metric and failed its exact assertion; header handling was +fixed while retaining all 49 checks. Raw exploratory outcomes remain separate from +final passed receipts. No native failure was skipped or ignored. + +Private evidence/caches stay outside Git and source archives. G5 and earlier archives +remain verified unchanged. No sibling source, remote branch, PR, merge, tag, release, +deployment, publication or external message was changed in this increment. diff --git a/Documentation/Architecture/Release1.6/G7Qualification.md b/Documentation/Architecture/Release1.6/G7Qualification.md new file mode 100644 index 0000000..3d7cc48 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G7Qualification.md @@ -0,0 +1,126 @@ +# G7 production reconciliation and qualification + +The isolated `codex/1.6-schema-examples` branch integrates main repair commit +037e735dc6224789e4d04d2c18d15b8d27440198 through local merge +b5bc082f930cad669364de61da4e4191e7bb9cc3. G6 remains at +4368947c0db3831da988b9d8626055a38b42a7e3. The new bounded grammar tests are +cc9ce4f49ca11bbb09273c0102c138c367cba2bc; the consumer scratch-path correction is +6652f9bd65b6f21e1a0c8a22fde5d0ce03b68813. Earlier gates and archives are preserved. + +## Source and behavior + +Main's best-effort diagnostic omission repair preserves an invalid verdict without +poisoning a cumulative ledger; owned work, annotation limits, overflow and cancellation +still fail operationally. Its native regression cases are retained. All 126 restored +maintenance function identities, structured HTML previews, exact policy counts and +49 core probes remain required. The deterministic CLI identity is regenerated from +this package's actual Sources and manifest. + +G7 adds two meaningful Swift Testing functions and a public generated-schema replay: +64 bounded grammar schemas, 512 seed/profile certifications and 64 known-invalid +mutations, plus scalar-key/schema-member permutation checks. A separate seeded +research grammar produces another 64 schemas/512 outputs/64 invalid mutations. +Neither is an exhaustive satisfiability or arbitrary-schema generation claim. + +The complete discovery contract requires 644 native functions, 628 Linux functions, +540 WASI functions, 325 native/Linux parameter invocations and 288 WASI invocations. +Native shared-state safety requires 45 functions. S3 itself has 98 functions and four +shared-generator functions. Exact module identities and every earlier parameterized +function count remain checked; policy exclusions never count as evaluated successes. + +The full native harness now builds the S3 consumer with the explicit root `.build` +scratch path used by its later ownership/generation/matrix helpers. An inspected +SwiftPM default would otherwise place it under the example package. The fix changes +one command argument pair and no runtime, test, cap or conformance policy. + +## Observed qualification + +The complete default native lane passed all 37 commands from an actual clean, +Git-free source extraction. Debug and Release each passed all 644 functions and +325 parameter invocations. The full pinned qualification executable passed with +its unchanged 900-second watchdog. All command exits, raw logs and structured +event streams are retained; source hashes were unchanged throughout. + + +Native host: macOS 27.0 arm64; Xcode 27.2 (27B5019j); Apple Swift 6.4 +(swiftlang-6.4.0.34.1 clang-2100.3.34.1); SDK 27.2; language mode 6. +Every native command explicitly selects DEVELOPER_DIR and SDKROOT. + +- Focused native lane: all ten commands passed, including 98 Debug and 98 Release + S3 functions, one ownership positive/eight negatives, 15 original public cases/two + public-access negatives, ten compiled guide snippets in two processes, G3/G4 + replay and the 512-record matrix in two processes. +- Full safety: 644 Release ASan functions/325 parameter invocations, 45 TSan functions + and strict-memory-safety compilation with warnings as errors passed. +- Full WASI: all 77 checks passed using Swift.org 6.4.0, the matching WASI SDK, Node + and WasmKit. Node ran all 540 functions/288 parameter invocations in Debug and + Release; existing public consumer/49-cap/rooted-vector/corpus/strict-memory checks + retained their explicit host scopes. This does not claim a WASI CLI runtime. +- Public S3 platform consumer: both mobile SDK builds; physical iPhone 14 Pro Max + on iOS 27.2; iPhone/iPad iOS 27.0 simulators; Node and WasmKit all passed 15 public + cases, ten guide snippets and 512 certifications/64 invalid mutations. All 512 + normalized records, including exact values, traces, attempts and work, match byte + for byte across those five executions and native macOS (six platforms total). + +The research oracle remains Python-only tooling. The original packet script reran +44 schemas/154 witnesses. An exact numeric binding reproduces the original known +integral-number expectation error. The preserved G0 amendment evaluates 155 witnesses +with zero disagreements and eight numeric-binding regressions. The generated-value +oracle agrees with all 1,328 checked output records and 128 negative mutations; +32 explicit incomplete-search rows are separate and are not evaluated successes. + +## Failures and scope + +The first native aggregate rejected a SwiftPM lock-wait preamble before manifest JSON. +A later default root Debug command reached its unchanged 1800-second watchdog on the +busy shared host. A cold pilot completed its Debug command in 1277 seconds but one +Apple PDF case threw timedOut. That unchanged case passed in isolation in 3.05 seconds, +and all 97 HTML functions passed in a subsequent bundle run. Original failed receipts +remain failed and separate from later full default-lane outcomes. + +Linux ARM64 preflight/build ran on actual Ubuntu 24.04/glibc with pinned Swift 6.4, +but the guest was interrupted to protect shared disk space. Partial console output +is not a full Linux native/safety pass. The exact pinned compressed input and partial +receipts/build cache are retained for resumption. No native x86_64 host or hosted +qualification was executed for this source. Physical iPad remains deferred under the +existing accepted policy; its SDK/simulator observations do not imply device results. + +All logical retention weights remain distinct from heap/RSS. Charged owned work is +not a caller instruction or wall-clock guarantee. No controlled performance result, +new optimization, fallback engine, public API/default, platform expansion, remote +push/PR/merge, tag, publication or deployment is claimed by this increment. + +## Gate disposition and replay + +G7 is qualified for the observed Apple and WASI scopes and the independent oracle; +its full Linux native/safety portion remains blocked by local storage. The Linux +container command ended with exit 137 after the task-owned guest was stopped; +seven completed commands, including the Release build, had exit zero. The stale +partial receipt still says running and is preserved as interrupted evidence, not +rewritten into a pass. No complete 628-function Linux result or Linux safety result +exists for this source. The task guest was deleted and its temporary runtime was +stopped/unregistered. Pinned compressed inputs, source and build cache are retained. +An x86_64 Linux host is unavailable locally. Physical iPad remains deferred under +the accepted simulator policy. G8 review remains open and release_ready is false. + +The final report materializes the clean source archive, full native/safety/DocC +receipts, independent oracle, platform logs and source mappings outside public Git. +Metadata-only successor changes do not alter the qualified runtime/tests/fixtures. +The source/evidence manifest distinguishes those snapshots; it does not present a +metadata mapping as another full native execution. Final DocC outcomes are recorded +in the report after execution; no remote deployment is implied. + +Use the repository lanes with the supported native environment selected: + +```sh +python3 -B Scripts/ci.py --lane native +python3 -B Scripts/ci.py --lane safety +python3 -B Scripts/ci.py --lane docs +python3 -B Scripts/ci.py --lane schema-examples-qualification +python3 -B Scripts/ci.py --lane wasm +``` + +On pinned Swift 6.4 Linux, resume both linux and linux-safety after sufficient local +space is available, or use separately authorized hosted execution. Preserve 628 +root functions, 325 parameter invocations, 44 shared functions, all 49 caps and the +exact independent-suite policy split. A filtered or WASI pass does not close Linux. diff --git a/Documentation/Architecture/Release1.6/G8CorpusLifetime.md b/Documentation/Architecture/Release1.6/G8CorpusLifetime.md new file mode 100644 index 0000000..ad445a7 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G8CorpusLifetime.md @@ -0,0 +1,38 @@ +# G8 synthetic corpus lifetime repair + +The first hosted S3 qualification at c629114 passed all 98 generator behavior +functions on both Linux architectures, but the complete ASan command exited 1. +Linux LeakSanitizer reported 129 bytes in four allocations from the stored +`JSONSchemaExampleCorpus.synthetic` initializer. Those failed receipts remain +failed. See [the initial Linux run](https://github.com/ThorFuchs/Swift-JSON/actions/runs/38822602731). + +The fixed corpus is now a read-only computed static property. Each access constructs +the same six ordered values for the caller's lifetime: null, false, zero, "example", +empty array and empty object. The original global cache is removed. This is a source +compatible getter for callers; the unreleased declaration changes from stored `let` +to computed `var`. No setter, approximate bridge or extra product is introduced. + +Caller corpus/options construction still precedes checked preparation. Preparation +admits and hashes every value under its existing cumulative ledger. This repair +changes neither corpus contents/order, transport versions, seed/profile choices nor +resource policies. It does not promise an allocation count, process RSS bound or +controlled performance result. The compiler/runtime report does not by itself prove +an invocation-owned reference cycle; the repair removes process-lifetime JSON storage. + +The existing exact-corpus Swift Testing case now exercises repeated reads, exact +encoding and independent caller mutation. Its original digest/order assertions and +all other functions remain required. No test count, cap, timeout or sanitizer check +is reduced. A separate generated public consumer performs 128 scoped corpus/options +reads with exact encoding/copy assertions under Release ASan. Linux explicitly enables +leak detection with no leak suppressions. It must exit zero and emit the exact marker. + +`Scripts/ci.py --lane safety` appends this process check after the existing full +ASan, TSan and strict-memory commands. `linux-safety` retains its runtime diagnostic +and all four safety commands. Existing exact requirements remain 644 Mac / 628 Linux / +540 WASI functions, 325 Mac/Linux / 288 WASI parameter invocations, 45 Mac / 44 Linux +sharing functions and all 49 core cap probes. Native QA and fresh hosted Linux/native +checks qualify the changed source; earlier static-corpus results are baseline evidence. + +Current-main composition/navigation integration and combined-source G8 review remain +separate. No sibling checkout, protection, tag, release or documentation deployment +is changed by the repair. diff --git a/Documentation/Architecture/Release1.6/G8Integration.md b/Documentation/Architecture/Release1.6/G8Integration.md new file mode 100644 index 0000000..df60a61 --- /dev/null +++ b/Documentation/Architecture/Release1.6/G8Integration.md @@ -0,0 +1,42 @@ +# G8 combined-source reconciliation + +The S3 caller-lifetime repair ends at `8d0e83037cb1e82b33e1dec862123c890b9b9bc7`. +Its standalone Native run 38824896580 and ARM64/x86_64 Linux run 38824893590 passed, +including ASan/LSan without suppressions. Those receipts and the earlier G0–G7 +records remain immutable evidence of their named source. + +This successor integrates main `7d72c8825982550f0ee87218136fda12d8bae13b`: +composition #35 and Navigation #38. It changes neither implementation’s semantics. +No sibling branch or checkout is modified. The feature-branch integration does +not merge PR #39 into main, tag, publish or deploy documentation. + +## Conflict decisions + +- Preserve all nine products: eight libraries and swift-json. JSONSchemaExamples + depends only on JSON/JSONSchema; Navigation only on JSON. Core imports neither. + The root dependency graph stays empty and private runtime targets stay private. +- Union exact root, shared-state, coverage and WASI inventories. Composition adds + 12 integration functions; Navigation adds 39 functions and one sharing function. + All 98 S3 functions, four sharing functions, 126 restored maintenance identities, + per-function parameter cardinalities and 49 baseline cap probes remain required. +- Preserve focused composition, Navigation, S3 ownership/public-consumer/matrix + and lifetime probes. The composition five-success/three-refusal capacity policy + remains explicit. No quota, sanitizer, exclusion, timeout or review trigger changes. +- Union eight DocC catalogs, routes, public API/resource descriptions and original + contribution notices. Keep stage-specific qualification history source-bound. +- Recompute CLI identity from the combined Package.swift and Sources bytes. + Neither parent’s old generated stamp describes this successor. + +The exact combined requirements are 695 Mac / 679 Linux / 591 WASI root functions, +325 Mac/Linux / 288 WASI parameter invocations and 47 Mac / 46 Linux TSan functions. +These are inventories, not claims that an unexecuted lane passed. Required check +names and protection settings remain unchanged. All native/safety/docs/archive and +both Linux architecture receipts must bind to this combined source. Available WASI +and Apple consumer checks supplement those gates. G8 acceptance and remaining +limitations are recorded with the final source/archive and materialized receipts; +Progress.json is updated only from actual outcomes. + +Logical retention remains distinct from process RSS. No controlled throughput, +caller instruction or arbitrary-schema completeness claim is added. Physical iPad +qualification retains the previously accepted simulator policy. Tagging and release +publication remain separate owner decisions. diff --git a/Documentation/Architecture/Release1.6/G8Qualification.md b/Documentation/Architecture/Release1.6/G8Qualification.md new file mode 100644 index 0000000..8282cbb --- /dev/null +++ b/Documentation/Architecture/Release1.6/G8Qualification.md @@ -0,0 +1,76 @@ +# G8 combined-source qualification and review + +Reviewed source `130823a5c8af47e21251f271df50a948c73cb9a1` integrates main +`7d72c8825982550f0ee87218136fda12d8bae13b` with repaired S3 parent +`8d0e83037cb1e82b33e1dec862123c890b9b9bc7`. The actual PR merge-ref source is +`34efc30e56da30a1e907a8f7d8b6614fd97b2951`; every public source-input hash matches. +This record is a documentation-only successor. Final PR checks and materialized +receipts bind its final archive separately; executable inputs and declarations stay +identical. Earlier G0–G7 and lifetime-repair records are retained as history. + +[Native run 38829233646](https://github.com/ThorFuchs/Swift-JSON/actions/runs/38829233646) +and [Linux run 38829233447](https://github.com/ThorFuchs/Swift-JSON/actions/runs/38829233447) +passed all eight qualification jobs. Branch Pages deployment is skipped. Commands +run through `Scripts/ci.py`; full native/Linux jobs additionally run `--lane composition`. +All actual commands, exits, event streams, source-input hashes and environments are +materialized in the maintainer review bundle; expected compiler failures remain +explicit nonzero outcomes, not silently ignored failures. + +| Actual scope | Outcomes | +| --- | --- | +| Native lane | 38 commands; 695 functions and 325 parameter invocations in each of Debug/Release | +| Native safety | Four commands; ASan 695/325, TSan 47, strict-memory warnings-as-errors, 128-scope corpus lifetime probe | +| Linux, each architecture | 34 commands; 679/325 Debug/Release; pinned Swift6.4 Ubuntu24.04 image | +| Linux safety, each architecture | Five commands; ASan 679/325, TSan46, strict-memory, explicit ASan/LSan lifetime probe without suppressions | +| Composition, each full job | Nine commands; 12 core functions, five external functions Debug/Release/ASan, eight cells (five completed, three operational refusals), 11 compiler negatives | +| Native documentation | Eight catalogs, 1,077 authored declarations documented, 14 README fences; S3 ten guide snippets in two processes | +| Native archive | Byte-equal archive/rebuild/extracted rebuild; full 38-command native from a real Git-free extraction | +| Linux extraction, each architecture | Release build, six focused integrity/resource functions and real public consumer; full 679-function root runs are separate | +| WASI | Matching Swift6.4 SDK; 79 commands; Node Debug/Release591 functions and288 parameters; strict memory, 49 caps, public vectors and independent suites; WasmKit public scope remains distinct | +| Apple mobile | S3 and Navigation compiled for both mobile SDKs; iPhone/iPad simulators each ran15 public cases, ten guides and512 matrix certifications with64 negatives | + +Native hosted execution used macOS27.0.1 arm64, Xcode27.2/SDK27.2 and +Apple Swift6.4 (`swiftlang-6.4.0.34.1`); local checks used macOS27.0 with the +same compiler/SDK baseline. Linux ARM64/x86_64 used official Swift6.4 on +Ubuntu24.04.5/glibc. WASI used the installed matching Swift.org6.4 SDK and both +recorded Node/WasmKit runtimes. No baseline, timeout, exclusion, cap or review +trigger was weakened. + +The pinned schema suite still distinguishes 2,300 evaluated successes from37 +policy cases (2,337 observed); JSONTestSuite287 from31 (318 observed). All126 +restored maintenance identities and49 cap probes remain required. Policy cases +are not successes. The amended44-schema/155-witness fixture remains unchanged. +Native fixed replay has816 certifications and32 explicit incompletes, plus two +separate trivial proofs; the additional seeded grammar has512 certifications, +64 negative cases and zero exact external-oracle disagreements. Five current +platform/engine matrix outputs match preserved values, traces, attempts, digests +and charged work byte-for-byte. Incompletes do not prove unsatisfiability. + +## Review decisions and boundaries + +The [12 conflict decisions](G8Integration.md) preserve composition, Navigation, +S3, every parent provenance notice, finite policy and required check name. Source +blob checks prove the S3 implementation/tests/fixtures/consumer match its repaired +parent, and core JSON/composition/Navigation match main. Compiler graphs preserve +all60 S3 declarations and all existing repaired-parent declarations. Navigation's +56 declarations remain unchanged. Nine products and23 targets retain an empty +root dependency graph; runtime targets remain private. CLI identity is regenerated +from the actual combined Package.swift/Sources manifest. + +The source/API/resource/lifecycle review found no further runtime repair needed. +Certification still requires a complete original-root native verdict. False +checks retain work; operational failures and cancellation remain terminal. Exact +numbers/scalars, immutable offline plans, iterative construction and finite +shared ledgers remain authoritative. No new public composition adapter is implied. + +This is agent review, not independent human approval. The physical iPhone was +disconnected, so no new device pass is claimed; its historical G7 receipt retains +its own source boundary. Physical iPad retains the previously accepted simulator +policy. Logical retention is not RSS, functional timing is not controlled +throughput, and arbitrary-schema solving remains incomplete. Source archive +`ab4cc31d95d3409d7947aafa057954089961fd6ff65e573e43ce92dd03f2a29c` +contains1,177 public source files plus its manifest and was independently reproduced. +The final documentation-only successor receives its own archive and receipts. + +G8 is ready for owner review with those explicit limits. PR #39 remains draft; +merging, tagging, release publication and Pages deployment remain separate. diff --git a/Documentation/Architecture/Release1.6/Plan.md b/Documentation/Architecture/Release1.6/Plan.md new file mode 100644 index 0000000..6758771 --- /dev/null +++ b/Documentation/Architecture/Release1.6/Plan.md @@ -0,0 +1,88 @@ +# Release 1.6 — S3 schema examples + +The proposed addition is an opt-in `JSONSchemaExamples` library. It constructs +deterministic candidate JSON and returns a witness only after the existing native +validator completes with `isValid == true`. Incomplete search, unsupported +construction, sound trivial unsatisfiability and operational errors stay distinct. +No arbitrary-schema completeness or unsatisfiability claim is intended. + +The isolated branch is `codex/1.6-schema-examples`, pinned to main +`507dbf9ad4b545a5d60d7d6acf6b814795ec3099`. See +[source reconciliation](Reconciliation.md), [decisions](Decisions.md) and +[progress](Progress.json). The initial orientation and compiler probes are complete. G1 now has a working +explicit-candidate API, a package-only qualified plan bridge and a real public +consumer; see [the accepted declarations and scope](G1API.md). [G1 native qualification](G1Qualification.md) and [G2 determinism](G2Qualification.md) are preserved. G3 now implements [conservative typed construction](G3Construction.md); [its native qualification](G3Qualification.md) is complete. General schema solving remains outside this product. + +## Integration boundary + +Current main has six library products and `swift-json`. S3 will add one library +with dependencies on JSON and JSONSchema only. Existing products gain no dependency +on it. Private regex/filter/CLI targets, empty root dependencies, Swift tools/compiler +6.4, language mode 6, Apple 27 minima, Apache-2.0 and notices remain authoritative. +Dual DSL PR #31 is independent and is not a build or qualification prerequisite. +No DSL integration example is promised until those products actually land. + +## Reviewable increments + +| Gate | Work and acceptance | +| --- | --- | +| G0 | Pin release/main/worktrees, inspect compiled APIs, verify packet and fixture expectations, identify package boundaries | +| G1 | Review exact public declarations; add optional target/test target; implement the smallest qualified read-only plan bridge and real public consumer | +| G2 | Metered splitmix64-v1, exact ordering/construction, versioned corpus/snapshot transport and SHA-256, confined invocation state | +| G3 | Mandatory final certification, foundational 24 must-generate cases and two sound trivial proofs, keyword capability ledger | +| G4 | Ten additional must-generate cases, eight honest incomplete-search cases, qualified refs/composition/regex boundaries | +| G5 | One cumulative ledger across construction and all validation attempts, including completed false verdicts; overflow, retention, terminal failure and cancellation tests | +| G6 | Compiled public examples, DocC, README/API/limits documentation and optional-product isolation | +| G7 | Swift Testing Debug/Release, exact inherited discovery and all 49 caps, safety, pinned suites, determinism and applicable platform qualification | +| G8 | Source/diff/API/capability ledger, actual receipts and fixture outcomes, reproducible source and materialized review return | + +Keep G1/G2/G3 cohesive local commits. Split later reference/search/resource work +when its diff needs separate review. No passing scaffold tests or unimplemented +public success API should be introduced. The amended fixture set has 155 witnesses; +its 44 case classifications and generator acceptance gates are unchanged. + +The current harness requires 419 native functions, 325 parameter invocations and +15 shared-state TSan functions before S3 adds tests. These are current-source +requirements, distinct from the observed 1.5.0 counts of 395/199/14. Preserve +exact identities/cardinalities, all 49 metric probes and independent suite policies. +Fresh S3 qualification must cover its actual final source; old evidence is a baseline. + +Release notes/version metadata, tagging, publication and public activation are +later deliberate steps. This kickoff does not authorize or perform them. E11/E12, +AI/model services, importers, CLI expansion and complete regex inversion stay outside S3. + + +G2 implements `s3-candidates-v2`: metered SplitMix64, stable exact construction, +explicit versioned corpus/source/policy/complete-trace transports and compiler +ownership boundaries. See G2Determinism.md and the subsequent qualification record. +The isolated branch retains its G0/G1 base. Main independently advanced to 037e735 +with 1.5.1 repairs; rebasing/merging that train is a separate integration task +before final G7/G8 release qualification, not silently part of G2. + +G3 advances the candidate algorithm to `s3-candidates-v3` and complete trace to +`swift-json-example-trace-v2`; source/corpus/policy v1 transports are unchanged. +The [capability ledger](Capabilities.json) distinguishes the 24 G3 must-generate +cases and two sound proofs from the 18 remaining G4 cases. All returned witnesses +still require complete validation against the original immutable compiled root. + +G4 implements [conservative advanced proposals](G4Construction.md): contains +minima and bounded anchored ASCII patterns. The ten advanced required and eight +may-fail fixtures receive separate per-case native evidence. [G4 native qualification](G4Qualification.md) is complete; G5-G8 and main repair integration remain separate. + +G5 qualifies [cumulative resources and lifecycle](G5Resources.md), with a corrected +trace reservation, terminal latching at the invocation boundary and 17 additional +behavior cases. See [the native outcomes](G5Qualification.md). Candidate algorithm +v4 and all trace/source/corpus/policy bytes stay unchanged; G6-G8 remain open. + +G6 synchronizes all ten Swift blocks in five S3 DocC guides with a real public +consumer and compiles the new README fence in the seven-library consumer. The +[current API](API.md), [49-metric scope](SchemaExamplesLimits.md), inline comments +and guides describe the implemented subset and lifecycle. See [G6 qualification](G6Qualification.md) +and [documentation contract](G6Documentation.md). Runtime/defaults remain unchanged; +G7/G8 and main repair integration remain open. The conditional DSL example waits +for an available integrated product without touching its independent train. + +G8 reconciles composition and Navigation on main7d72c88 while preserving S3 and +all prior increments. The [combined-source qualification](G8Qualification.md) records +actual execution and the source/API/evidence review. It adds no release, merge, +physical-device or controlled-performance claim beyond its stated scopes. diff --git a/Documentation/Architecture/Release1.6/Progress.json b/Documentation/Architecture/Release1.6/Progress.json new file mode 100644 index 0000000..6f78051 --- /dev/null +++ b/Documentation/Architecture/Release1.6/Progress.json @@ -0,0 +1,437 @@ +{ + "schema_version": 1, + "target_release": "1.6.0", + "wave": "S3 JSONSchemaExamples", + "branch": "codex/1.6-schema-examples", + "initial_release_base": "8a5cf73dfe9b674f7c635f5f6d21a7051d66bb51", + "implementation_base": "507dbf9ad4b545a5d60d7d6acf6b814795ec3099", + "packet_sha256": "f67ec2f5d0bfd223f54a3577f93c03951a7992ecab524910ba44f6bc8621c4a1", + "status": "G8 agent source/API/evidence review complete; combined-source available-platform qualification passed; owner merge/release decisions remain separate", + "release_ready": false, + "gates": { + "S3-G0": "passed_with_documented_fixture_amendment", + "S3-G1": "passed_native_increment", + "S3-G2": "passed_native_increment", + "S3-G3": "passed_native_increment", + "S3-G4": "passed_native_increment", + "S3-G5": "passed_native_increment", + "S3-G6": "passed_native_increment", + "S3-G7": "passed_combined_native_linux_wasi_and_recorded_apple_scopes", + "S3-G8": "review_complete_with_explicit_platform_and_claim_limits" + }, + "observed": { + "packet_manifest_files": 26, + "published_baseline_source_files": 995, + "baseline_hosted_receipts_verified": 7, + "baseline_clean_native_receipts_verified": 1, + "baseline_command_logs_verified": 120, + "baseline_event_streams_verified": 24, + "original_cases": 44, + "original_witnesses": 154, + "original_expected_verdict_errors": 1, + "amended_witnesses": 155, + "amended_native_oracle_disagreements": 0, + "oracle_version": "python-jsonschema 4.26.0 with exact numeric binding", + "oracle_binding_regressions": 8, + "native_prng_golden_draws": 16, + "native_prng_range_checks": 640, + "compiled_package_boundary_probes": 3, + "original_fixture_sha256": "6f2bfd4fdb7c7e206b6570c2156ca8e248831840733b0258aa8cd5938b35649f", + "amended_fixture_sha256": "5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3" + }, + "inherited_current_harness_requirements": { + "native_root_functions": 419, + "parameter_invocations": 325, + "native_shared_state_functions": 15, + "core_metrics": 49, + "scope": "Required counts on current main, not a fresh S3 qualification claim" + }, + "next_gate": "Owner review/merge decision; post-merge qualification and release/tag/publication are separate", + "limitations": [ + "Physical iPhone is currently disconnected: no new physical-device pass is claimed; the historical G7 receipt remains source-bound. Fresh SDK and iPhone/iPad simulator checks passed.", + "Physical iPad retains the existing accepted simulator-only policy.", + "General constraint solving, boundary profile and advanced/dynamic construction remain explicitly incomplete.", + "Logical retention is not process RSS; no controlled throughput or caller instruction guarantee.", + "This is agent review and a draft PR; no independent human approval, PR merge, tag, release or deployment is claimed." + ], + "g1_observed": { + "new_swift_testing_functions": 21, + "existing_schema_functions": 69, + "schema_parameter_invocations": 12, + "evaluated_pinned_schema_cases": 2300, + "separate_schema_policy_cases": 37, + "public_consumer_cases": 12, + "expected_compiler_rejections": 2, + "release_address_sanitizer_functions": 21, + "release_thread_sanitizer_functions": 1, + "documented_new_public_declarations": 42, + "compiled_docc_catalogs": 7, + "algorithm": "s3-g1-candidates-v1", + "random_draws_per_invocation": 0, + "focused_native_lane_commands": 6, + "focused_safety_lane_commands": 3 + }, + "current_harness_requirements": { + "native_root_functions": 695, + "linux_root_functions": 679, + "wasi_root_functions": 591, + "parameter_invocations": 325, + "wasi_parameter_invocations": 288, + "native_shared_state_functions": 47, + "linux_shared_state_functions": 46, + "core_metrics": 49, + "scope": "Combined-source exact requirements; execution results remain source-bound" + }, + "g1_implementation_commit": "6c19580dec99c9363bb4b4d1611af8b8a2bab266", + "g1_qualification_record": "Documentation/Architecture/Release1.6/G1Qualification.md", + "g2_base_commit": "8de39d55675180def26184f24b0eeb87feb214bd", + "g2_qualification_record": "Documentation/Architecture/Release1.6/G2Qualification.md", + "g2_observed": { + "generator_algorithm": "s3-candidates-v2", + "rng_algorithm": "splitmix64-v1", + "new_swift_testing_functions": 21, + "total_generator_functions": 42, + "debug_functions": 42, + "release_functions": 42, + "release_address_sanitizer_functions": 42, + "release_thread_sanitizer_functions": 1, + "concurrent_invocations_in_thread_test": 32, + "public_consumer_cases": 15, + "public_consumer_processes": 2, + "public_compiler_rejections": 2, + "implementation_compiler_positive_probes": 1, + "implementation_compiler_rejections": 5, + "independent_transport_profiles": 2, + "policy_generation_caps_perturbed": 49, + "native_prng_golden_draws": 16, + "native_prng_bounded_selections": 640, + "schema_functions_reused_with_unchanged_core_source": 69, + "schema_parameter_invocations": 12, + "evaluated_pinned_schema_cases": 2300, + "separate_schema_policy_cases": 37, + "harness_mutation_cases": 17, + "testing_event_parser_cases": 19, + "focused_determinism_commands": 7, + "focused_safety_commands": 3 + }, + "g3_base_commit": "42a523c6d0a4dab9195c847feb4e47b43803bfd1", + "g3_qualification_record": "Documentation/Architecture/Release1.6/G3Qualification.md", + "g3_observed": { + "generator_algorithm": "s3-candidates-v3", + "trace_algorithm": "swift-json-example-trace-v2", + "new_swift_testing_functions": 19, + "total_generator_functions": 61, + "debug_functions": 61, + "release_functions": 61, + "release_address_sanitizer_functions": 61, + "release_thread_sanitizer_functions": 2, + "concurrent_invocations_per_thread_function": 32, + "g3_must_generate_cases": 24, + "profiles": 2, + "seeds": 4, + "generated_per_process": 192, + "public_acceptance_processes": 2, + "witness_assertions_per_process": 90, + "separate_trivial_proofs": 2, + "remaining_g4_cases_not_counted": 18, + "integer_rounding_grid_cases": 2940, + "public_consumer_cases": 15, + "public_compiler_rejections": 2, + "implementation_compiler_positive_probes": 1, + "implementation_compiler_rejections": 8, + "focused_native_commands": 8, + "focused_safety_commands": 3, + "harness_mutation_cases": 22, + "testing_event_parser_cases": 19, + "unchanged_schema_source_files": 654, + "reused_schema_functions": 69, + "reused_schema_parameter_invocations": 12, + "reused_evaluated_pinned_schema_cases": 2300, + "separate_schema_policy_exclusions": 37 + }, + "g4_base_commit": "3951b99bb0f690098cf8bef61ce872119e4835da", + "g4_qualification_record": "Documentation/Architecture/Release1.6/G4Qualification.md", + "g4_observed": { + "generator_algorithm": "s3-candidates-v4", + "trace_algorithm": "swift-json-example-trace-v2", + "new_swift_testing_functions": 18, + "total_generator_functions": 79, + "debug_functions": 79, + "release_functions": 79, + "release_address_sanitizer_functions": 79, + "release_thread_sanitizer_functions": 3, + "concurrent_invocations_per_thread_function": 32, + "additional_must_generate_cases": 10, + "honest_may_fail_cases": 8, + "profiles": 2, + "seeds": 4, + "g4_invocations_per_process": 144, + "g4_required_successes_per_process": 80, + "g4_may_valid_per_process": 32, + "g4_explicit_incomplete_per_process": 32, + "public_acceptance_processes": 2, + "full_native_fixture_schemas": 44, + "full_native_witness_expectations_per_process": 155, + "g3_required_regression_successes_per_process": 192, + "g3_separate_trivial_proofs": 2, + "public_consumer_cases": 15, + "public_compiler_rejections": 2, + "implementation_compiler_positive_probes": 1, + "implementation_compiler_rejections": 8, + "focused_native_commands": 9, + "focused_safety_commands": 3, + "harness_mutation_cases": 26, + "testing_event_parser_cases": 19, + "classified_compiled_keywords": 59, + "capability_groups": 16, + "unchanged_schema_source_files": 654, + "reused_schema_functions": 69, + "reused_evaluated_schema_cases": 2300, + "separate_schema_policy_exclusions": 37 + }, + "g5_base_commit": "304f900489dc6c24bce862eccf6adc9865df1027", + "g5_qualification_record": "Documentation/Architecture/Release1.6/G5Qualification.md", + "g5_observed": { + "generator_algorithm": "s3-candidates-v4", + "trace_algorithm": "swift-json-example-trace-v2", + "new_swift_testing_functions": 17, + "total_generator_functions": 96, + "debug_functions": 96, + "release_functions": 96, + "release_address_sanitizer_functions": 96, + "release_thread_sanitizer_functions": 4, + "concurrent_invocations_per_sharing_function": 32, + "new_mixed_success_and_cancelled_invocations": 32, + "cumulative_exact_and_one_short_metrics": 7, + "limits_scope_reviewed": 49, + "public_acceptance_processes": 2, + "g3_required_calls_per_process": 192, + "g4_calls_per_process": 144, + "g4_certified_per_process": 112, + "g4_explicit_incomplete_per_process": 32, + "native_fixture_schemas": 44, + "native_witness_expectations_per_process": 155, + "focused_native_commands": 9, + "focused_safety_commands": 3, + "public_consumer_cases": 15, + "public_compiler_rejections": 2, + "implementation_positive_probes": 1, + "implementation_compiler_rejections": 8, + "harness_mutation_cases": 26, + "testing_event_parser_cases": 19, + "unchanged_core_files": 654, + "reused_schema_functions": 69, + "reused_evaluated_schema_cases": 2300, + "separate_schema_policy_exclusions": 37, + "unmatched_trace_refund_removed_bytes": 32, + "default_caps_changed": false, + "public_signatures_changed": false + }, + "g6_base_commit": "30cf344c674a7514db2a578397ade642c0ab942e", + "g6_qualification_record": "Documentation/Architecture/Release1.6/G6Qualification.md", + "g6_observed": { + "generator_algorithm": "s3-candidates-v4", + "public_signatures_or_defaults_changed": false, + "production_swift_files_compared": 181, + "production_doc_comment_only_files": 3, + "root_manifest_and_existing_tests_unchanged": true, + "swift_testing_functions": 96, + "debug_functions": 96, + "release_functions": 96, + "release_address_sanitizer_functions": 96, + "release_thread_sanitizer_functions": 4, + "focused_native_commands": 9, + "focused_safety_commands": 3, + "compiled_s3_guide_snippets": 10, + "s3_guides": 5, + "documentation_processes": 2, + "compiled_readme_fences": 14, + "documentation_harness_mutations": 11, + "existing_ci_harness_mutations": 26, + "testing_event_parser_regressions": 19, + "classified_metrics": 49, + "public_consumer_cases": 15, + "public_access_compiler_negatives": 2, + "ownership_compiler_positive": 1, + "ownership_compiler_negatives": 8, + "g3_required_calls_per_process": 192, + "g4_calls_per_process": 144, + "g4_valid_per_process": 112, + "g4_explicit_incomplete_per_process": 32, + "native_fixture_witness_expectations_per_process": 155, + "conditional_dsl_example": "deferred: product absent from inspected S3 package and main base; sibling train untouched", + "release_ready": false + }, + "historical_current_states": [ + { + "gate": "S3-G6", + "status": "G6 complete: synchronized compiled public guides, current API/README/DocC and all 49 metric scopes native-qualified", + "current_harness_requirements": { + "native_root_functions": 515, + "parameter_invocations": 325, + "native_shared_state_functions": 19, + "core_metrics": 49, + "scope": "Full-lane requirements; focused G5 observations recorded separately" + }, + "next_gate": "G7 main-repair integration, full native/safety/pinned-suite/oracle/platform qualification; G8 final review return", + "limitations": [ + "G7/G8 remain open; this is not a qualified 1.6 release candidate", + "Original packet expectation/oracle representation amendment remains separately preserved", + "No new full nine-bundle Native/safety, Linux, WASI, mobile or controlled-performance pass is claimed", + "Main independently advanced to 037e735 (1.5.1 repairs); this isolated G6 branch preserves its G0/G1 base pending later integration.", + "G2-G6 are native-only qualification: Linux/WASI/mobile goldens remain G7; no controlled throughput or RSS claim", + "General constraint solving, boundary profile and advanced/dynamic construction remain explicitly incomplete", + "Historical exploratory/compiler/test/harness attempts are retained separately from final passed receipts" + ] + }, + { + "gate": "S3-G7-local", + "status": "G7 main repairs reconciled; full Git-free native/safety, oracle and Apple/WASI qualification passed; full Linux native/safety blocked by local storage", + "gates": { + "S3-G0": "passed_with_documented_fixture_amendment", + "S3-G1": "passed_native_increment", + "S3-G2": "passed_native_increment", + "S3-G3": "passed_native_increment", + "S3-G4": "passed_native_increment", + "S3-G5": "passed_native_increment", + "S3-G6": "passed_native_increment", + "S3-G7": "qualified_available_scopes_linux_blocked", + "S3-G8": "not_started" + }, + "limitations": [ + "G7 Linux full native/safety remains blocked by local disk; seven commands including Release build passed before guest interruption (exit137), not a full test pass", + "No local native x86_64 Linux qualification; physical iPad deferred under existing simulator acceptance policy", + "G8 remains open and this increment is not a qualified 1.6 release candidate", + "General constraint solving, boundary profile and advanced/dynamic construction remain explicitly incomplete", + "No controlled throughput, heap/RSS or caller instruction guarantee", + "All exploratory/harness failures remain separate from later passed receipts; no push, PR, release or deployment" + ] + } + ], + "g7_base_commit": "4368947c0db3831da988b9d8626055a38b42a7e3", + "g7_integrated_main_commit": "037e735dc6224789e4d04d2c18d15b8d27440198", + "g7_qualified_source_commit": "6652f9bd65b6f21e1a0c8a22fde5d0ce03b68813", + "g7_qualification_record": "Documentation/Architecture/Release1.6/G7Qualification.md", + "g7_observed": { + "default_git_free_native_commands": 37, + "native_debug_functions": 644, + "native_release_functions": 644, + "native_parameter_invocations": 325, + "release_address_sanitizer_functions": 644, + "thread_sanitizer_functions": 45, + "strict_memory_safety_warnings_as_errors": true, + "s3_functions": 98, + "s3_shared_functions": 4, + "restored_maintenance_function_identities": 126, + "core_public_cap_probes": 49, + "fixed_grammar_schemas": 64, + "certifications_per_process": 512, + "known_invalid_per_grammar": 64, + "separate_randomized_grammar_schemas": 64, + "external_generated_certifications": 1328, + "external_known_invalid": 128, + "external_disagreements": 0, + "amended_fixture_schemas": 44, + "amended_fixture_witnesses": 155, + "oracle_numeric_binding_regressions": 8, + "evaluated_pinned_schema_cases": 2300, + "separate_schema_policy_cases": 37, + "evaluated_pinned_json_cases": 287, + "separate_json_policy_cases": 31, + "wasi_checks": 77, + "node_debug_functions": 540, + "node_release_functions": 540, + "wasi_parameter_invocations": 288, + "mobile_sdk_builds": 2, + "simulator_runtimes": 2, + "physical_iphone_runs": 1, + "cross_platform_byte_equal_matrix_hosts": 6, + "linux_completed_commands": 7, + "linux_guest_command_exit": 137, + "linux_full_native_or_safety_pass": false, + "controlled_performance_claim": false, + "public_signature_or_default_changes": false + }, + "g8_initial_head": "c6291148f9ba31b48b9a879b0eb004087f567c96", + "g8_lifetime_record": "Documentation/Architecture/Release1.6/G8CorpusLifetime.md", + "g8_initial_linux_run": "https://github.com/ThorFuchs/Swift-JSON/actions/runs/38822602731", + "history": [ + { + "gate": "S3-G8-corpus-lifetime", + "source": "8d0e83037cb1e82b33e1dec862123c890b9b9bc7", + "status": "passed_standalone_native_linux_safety_docs_archive", + "native_run": 38824896580, + "linux_run": 38824893590, + "limitations": [ + "These runs do not qualify the later composition/Navigation combination; preserved materialized G8 preparation report provides raw receipts." + ] + }, + { + "gate": "S3-G8-integration-preparation", + "status": "G8 combined-source reconciliation with main; fresh qualification in progress", + "gates": { + "S3-G0": "passed_with_documented_fixture_amendment", + "S3-G1": "passed_native_increment", + "S3-G2": "passed_native_increment", + "S3-G3": "passed_native_increment", + "S3-G4": "passed_native_increment", + "S3-G5": "passed_native_increment", + "S3-G6": "passed_native_increment", + "S3-G7": "reopened_for_combined_source_qualification", + "S3-G8": "review_preparation_in_progress" + }, + "source": "130823a5c8af47e21251f271df50a948c73cb9a1" + } + ], + "g8_integrated_main": "7d72c8825982550f0ee87218136fda12d8bae13b", + "review_ready": true, + "actions": { + "feature_branch_pushed": true, + "draft_pr": 39, + "pr_merged": false, + "tag_created": false, + "release_published": false, + "pages_deployed": false, + "sibling_edits": false + }, + "g8_qualified_source_commit": "130823a5c8af47e21251f271df50a948c73cb9a1", + "g8_qualified_pr_merge_commit": "34efc30e56da30a1e907a8f7d8b6614fd97b2951", + "g8_qualification_record": "Documentation/Architecture/Release1.6/G8Qualification.md", + "g8_source_archive_sha256": "ab4cc31d95d3409d7947aafa057954089961fd6ff65e573e43ce92dd03f2a29c", + "g8_observed": { + "native_commands": 38, + "linux_commands_per_architecture": 34, + "composition_commands_per_full_job": 9, + "native_debug_release_asan_functions": 695, + "linux_debug_release_asan_functions": 679, + "parameter_invocations_each": 325, + "native_tsan_functions": 47, + "linux_tsan_functions": 46, + "restored_maintenance_identities": 126, + "finite_core_cap_probes": 49, + "docc_catalogs": 8, + "authored_documented_symbols": 1077, + "s3_authored_declarations": 60, + "navigation_authored_declarations": 56, + "wasi_commands": 79, + "wasi_debug_release_functions": 591, + "wasi_parameter_invocations_each": 288, + "sdk_builds_s3": 2, + "sdk_builds_navigation": 2, + "simulators": 2, + "fresh_physical_device_runs": 0, + "fixed_native_records": 848, + "fixed_certified": 816, + "fixed_explicit_incomplete": 32, + "separate_trivial_proofs": 2, + "additional_research_certified": 512, + "additional_research_invalid": 64, + "external_oracle_disagreements": 0, + "byte_equal_current_matrix_platforms": 5, + "expected_ownership_rejections": 8, + "public_consumer_cases": 15, + "public_access_rejections": 2, + "compiled_guide_snippets": 10, + "source_review_new_runtime_findings": 0 + } +} diff --git a/Documentation/Architecture/Release1.6/Reconciliation.md b/Documentation/Architecture/Release1.6/Reconciliation.md new file mode 100644 index 0000000..3a36e0e --- /dev/null +++ b/Documentation/Architecture/Release1.6/Reconciliation.md @@ -0,0 +1,104 @@ +# S3 source and input reconciliation + +Observed on 2026-10-15. The private raw receipts and probes are retained under +ignored `Artifacts/SchemaExamplesS3` in the isolated checkout. This record closes +orientation; it does not claim a generator implementation or release qualification. + +## Releases and independent trains + +GitHub reports 1.5.0 published and latest at +`8a5cf73dfe9b674f7c635f5f6d21a7051d66bb51`. The protected 1.4.0 tag points to +`e8c3e4bd9ae40d797f07a527ea98879acee3c057`; its separate release remains draft. +The CLI's frozen `1.4.0-dev`/unknown-revision metadata is an explicitly disclosed +1.5.0 release limitation, not grounds to rewrite published source or old receipts. + +Initial local main and origin/main matched 1.5.0 and were clean. All 23 registered +worktrees, including the newly created S3 checkout, were inspected; no visible +local changes were found at that observation. Historical qualification worktrees +were retained. E2 and the CLI are already integrated into main; old branch ancestry +alone is not evidence that squash-merged features remain unintegrated. + +During orientation, the sibling session merged HTML previews PR #30 as +`507dbf9ad4b545a5d60d7d6acf6b814795ec3099`. Only the new, still-clean S3 branch +was fast-forwarded before its first tracked edit. Original main was left at +1.5.0. JSON, JSONSchema and JSONRegexRuntime source trees are byte-identical +between those commits. The manifest delta adds JSONHTMLTests fixture resources; +the seven products, empty dependency graph and JSON/schema target edges are unchanged. +The HTML/CLI behavior delta and its current CI belong to that separate merged train. + +Dual DSL PR #31 remains draft at `64264218dc0073a5738ab557bfa25b3bb9f6fa7a` +and reports conflicts after main advanced. Neither its files nor its state were +changed. S3 imports neither proposed DSL product. Future main movement requires +an explicit integration check; this feature does not continuously reset to it. + +Strict required checks remain `Native / native`, `Native / safety` and +`Native / docs`, with PRs, resolved review threads and no bypass. No protection, +tag, release or remote branch configuration was changed by this kickoff. + +## Actual baseline evidence + +The downloaded published source is SHA-256 +`b3ec3ede09b448a32dceaf8ba89f97f99458e3dade9e3c4236d043e30e1d4389`, +1,501,990 bytes, 995 source files plus manifest. A local sorted normalized archive +of the untouched initial checkout reproduced those exact bytes. + +The materialized 1.5.0 evidence archive is +`afe7ac0ebd07c7fe963f9f042db090a6e547e9abbdd2898bf174a9d7fc528a76`. +Verification covered 455 manifested evidence files, seven hosted CI receipts and +one clean-extraction native receipt, 120 command logs and 24 structured event +streams. All 995 source hashes matched the released checkout. This reuses actual +passed baseline evidence; no new uninterrupted full Native/S3 lane is inferred. +Linux receipts omit direct Git SHA; their complete source hashes and published +workflow identity remain the documented identity evidence. + +This machine passed native preflight: macOS 27.0 arm64, Xcode 27.2 build 27B5019j, +Apple Swift 6.4 (swiftlang-6.4.0.34.1), SDK 27.2, language mode 6. Probes explicitly +select the matching Xcode/SDK, complete strict concurrency and warnings as errors. +No global developer-directory selection or compatibility flag was introduced. + +## Packet and fixture amendment + +Packet SHA-256 is +`f67ec2f5d0bfd223f54a3577f93c03951a7992ecab524910ba44f6bc8621c4a1`; +all 26 non-manifest files verified. Its inspected baseline was 9517233, before +the merged E2, CLI/provider and structured-preview increments. It contains design +proposals and research receipts, not implemented generator source. + +The unmodified Python 4.26.0 oracle passes its 44 cases/154 witnesses. Native replay +found one incorrect expectation: `exact-integer-beyond-2pow53/invalid_3` marks +numeric `9007199254740994.0` invalid for integer bounds 9007199254740993 through +9007199254740995. It is mathematically integral and within those bounds. This agrees +with [Draft 2020-12 type semantics](https://json-schema.org/draft/2020-12/json-schema-validation#section-6.1.1). + +The packet parses fractional tokens as Decimal, while python-jsonschema's default +integer binding excludes Decimal. The [oracle's own type-checking documentation](https://github.com/python-jsonschema/jsonschema/blob/main/docs/validate.rst) +describes this Python representation boundary. An exact research decoder now maps +only exactly integral Decimal tokens to exact Python int; other fractions remain +Decimal. No binary floating-point conversion is used. Eight separate binding +regressions include integral lexemes, negative zero, large values, a nonintegral +large value and Boolean exclusion. + +The original packet and its failing native comparison remain immutable. A separate +derived fixture corrects that verdict to true and adds `9007199254740994.5` as a +genuinely fractional negative. All original case classifications remain: 24 G3 +must-generate, 10 G4 must-generate, 8 G4 may-fail-explicitly and 2 G3 sound proofs. +All 155 amended witnesses agree between native Swift 6.4 and the exact-bound Python +oracle. This is fixture validation, not 155 generated examples or proof of completeness. +Original fixture SHA-256 is +`6f2bfd4fdb7c7e206b6570c2156ca8e248831840733b0258aa8cd5938b35649f`; +the derived hash and exact amendment are in the private receipt. + +## Compiled source boundaries + +A separate public-only consumer compiles against real JSON/JSONSchema, retains a +checked-Sendable plan/registry pair, exercises explicit registry resources, native +report and direct validation, and normalized scalar-order writer roundtrips. +Initial probe accessor/autoclosure errors and the original fixture failure are +retained separately from successful amended runs. + +Compiler-driven package probes pass with the actual SwiftPM package identity: +shared noncopyable meter validation, accounting-domain changes and portable +SHA-256 are accessible. Compiled node arrays are package-visible, but node.raw and +reference/child fields are internal. Intended negative probes fail for internal +node access and external package-plan access. G1 therefore needs a narrow +read-only qualified-node bridge, not a guessed URI resolver or broad graph exposure. diff --git a/Documentation/Architecture/Release1.6/SchemaExamplesLimits.md b/Documentation/Architecture/Release1.6/SchemaExamplesLimits.md new file mode 100644 index 0000000..dd7a1be --- /dev/null +++ b/Documentation/Architecture/Release1.6/SchemaExamplesLimits.md @@ -0,0 +1,49 @@ +# Schema example resource scope + +The current API uses the existing 49 finite metrics and version 1 logical weights. +Defaults in [ResourceProfiles.json](../ResourceProfiles.json) are unchanged. This +mapping describes when metrics apply, rather than claiming each metric charges +work in every generation. [G5 resources](G5Resources.md) defines ownership and +terminal behavior; [the compiled guide](../../../Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md) +demonstrates policy and checked remaining work using public APIs. + +| Metrics | Scope in schema examples | +| --- | --- | +| workUnits, numericWorkUnits | Cumulative preparation work, then a separate cumulative generation ledger; generation includes rejected candidates, native validations and final hash | +| evaluationWorkUnits, aggregateRegexWorkUnits | Cumulative over every native evaluation in one generation; included in workUnits | +| compileWorkUnits, compiledPlanBytes | Preparation compiler-domain work and logical reservations, including preparation serialization/hashing scratch | +| schemaDocuments, schemaBytes | Preparation root/registry admission and snapshot capacities | +| schemaNodes, referenceEdges | Preparation compilation plus bounded generation declaration/conjunction/reference discovery | +| retainedBytes | Conservative logical live reservations across all owned phases; release discarded ownership, never work; report certification and peak separately | +| inputBytes, outputBytes, nodes, aggregateTextBytes | Capacity per materialization; retire between values while preserving lifetime report totals and work | +| maxDepth, objectMembers, arrayItems | Before container/frame growth and each value's container cardinality; not lifetime generated-depth totals | +| stringBytes, keyBytes, numberDigits, numberTokenBytes, absoluteExponent | Individual exact token/value capacities and checked synthesis scratch; no implicit dense unlimited exponent expansion | +| evaluationFrames | Bounded pending discovery/construction and native evaluation state | +| issues, diagnosticBytes | Native diagnostic charges remain cumulative after false verdicts; diagnosticBytes also bounds complete abstract trace bytes | +| annotationEntries, annotationBytes | Native evaluated-location/output state; discarded live annotation bytes can be released while work/entry admission stays charged | +| regexPatternBytes | Preparation/native regex pattern admission and generation proposal-scanner source capacity | +| aggregateRegexPatternBytes, regexProgramInstructions, regexCompileWorkUnits, regexCaptureGroups | Native preparation regex compilation; not a new generation matcher or compiler | +| regexSearchWorkUnits, regexRetainedBytes | Native per-search ceiling and logical live state; per-search retirement does not reset aggregateRegexWorkUnits | +| pointerBytes, pointerTokens | Native compiler/evaluator location and diagnostic Pointer helpers | +| cancellationQuantum | Positive and at most 1,024; entry, owned growth and loop checkpoints also apply | +| sourceMapEntries | No new generator source map; caller-admitted document/source-map ownership precedes preparation | +| chunkBytes, pendingEvents, pendingValues, totalInputBytes, totalRecords, totalWorkUnits, totalScannedNodes, maxReusableBufferBytes | Streaming metrics; generation opens no stream or reusable streaming pool; entries remain finite and policy-bound | +| operations, patchWorkUnits | Patch-only metrics; generation performs no Patch operation | + +`maximumAttempts` is the additional finite search ceiling (default 16), not a 50th +JSONResourceMetric. Limits are inclusive and quota arithmetic is checked. Capacity-only +checks may report zero. Cancellation propagates as CancellationError, separately +from JSONFailure resources and JSONSchemaExampleFailure incomplete search/proofs. + +Preparation and generation are distinct operations. Each owns one ledger and no +nested helper resets cumulative work. A successful result reports certification; +remaining reservations end on transfer or failure. Conservative arena reservations +can exceed actual temporary liveness. Independent peaks must not be added as if they +were a simultaneous live bound. Logical bytes are not RSS, allocator size or retained +caller output; work units are not elapsed time or arbitrary caller instruction quotas. + +For a caller composition spanning several public calls, subtract successful aggregate +work once, preserve subsystem ceilings independently, and stop on an error/cancellation. +A failed call has no successful report; a fresh full quota would lose the cumulative +bound. Native boundary evidence and unchanged default policies do not prove hard RSS +or throughput guarantees. Full 49-probe/platform release execution remains G7. diff --git a/Documentation/Architecture/ResourceLimits.md b/Documentation/Architecture/ResourceLimits.md index 30cefad..56e9f27 100644 --- a/Documentation/Architecture/ResourceLimits.md +++ b/Documentation/Architecture/ResourceLimits.md @@ -122,6 +122,22 @@ retainedBytes. Scalar scanning and immutable plan-local regex sharing remain the qualified choices; no SIMD or prefilter optimization is claimed. Native performance observations are separate from the logical policy in [performance notes](../Performance/README.md). +## Optional schema example generation (1.6 development) + +JSONSchemaExamples uses the same 49 caps and logical weights, with a separate finite +preparation budget and one cumulative generation/evaluation budget per invocation. +All candidates, including false verdicts, share that generation ledger. Generation +limits intersect validation limits without changing evaluator policy; a positive +finite maximumAttempts (default 16) additionally bounds candidate checks. + +The [metric scope](Release1.6/SchemaExamplesLimits.md) distinguishes cumulative work, +per-materialization capacities, logical reservations and metrics for operations the +generator does not perform. The [compiled public guide](../../Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md) +demonstrates checked remaining work. Certification statistics include conservative +staging reservations; transfer or failure ends the invocation's ownership. A failed +call returns no successful report. No independent peak sums, RSS/heap hard caps or +caller instruction guarantees are implied. Default profiles are unchanged. + ## Experimental synchronous composition `JSON.withComposition` borrows a noncopyable, non-Sendable facade over one native diff --git a/Documentation/CI/README.md b/Documentation/CI/README.md index 01c1973..36770c0 100644 --- a/Documentation/CI/README.md +++ b/Documentation/CI/README.md @@ -1,3 +1,17 @@ +# Combined S3 / composition / Navigation qualification + +The current inventory requires 695 Mac / 679 Linux / 591 WASI functions, +325 Mac/Linux and 288 WASI parameter invocations, 47 Mac / 46 Linux shared-state +functions, eight library DocC catalogs, nine products and all 49 unchanged finite +cap probes. Composition’s external five-function suite, eight classified capacity +cells and 11 ownership rejections remain additive. Native / native also executes +the focused composition lane; both Linux architecture jobs retain it. +The S3 public/ownership/matrix/lifetime gates and Navigation external consumer are +all retained. Counts specify requirements; source-bound receipts establish passes. +See [G8 reconciliation](../Architecture/Release1.6/G8Integration.md). + +The following sections preserve their original increment histories. + # Native CI ## Reconciled Navigation and composition increment @@ -74,7 +88,7 @@ Pushes to main, pull requests and manual dispatch run four lanes: after execution. The extracted receipt must have null Git identity and the same complete source manifest. - Documentation: public symbol graphs, complete authored API/parameter comments, - executed README examples, six native DocC catalogs with warnings as errors, + executed README examples, seven native DocC catalogs with warnings as errors, native static-hosting transforms and site route/asset checks. Actions are pinned by commit. Jobs have read-only repository permissions and @@ -171,6 +185,58 @@ remain required. The first failed inherited expectations are retained; no blanke nonzero/exit-zero replacement is used. Real receiving-app and physical-device UI observations remain separate from automated SDK/API and portable execution. +## S3 G1 focused native lane + +`python3 Scripts/ci.py --lane schema-examples` checks the exact eight-product +manifest, empty dependencies, all 49 unchanged finite core policies, 21 functions +in JSONSchemaExamplesTests, all 69 existing JSONSchemaTests functions with their +12 exact parameter invocations, and the real standalone public consumer (12 cases and +2 expected package-boundary compiler rejections). This focused lane supplements +the unchanged required Native checks; it does not establish G7 platform release +qualification. It records source hashes, toolchain, commands, exits and events. + +The full root lane now requires 440 functions on Mac (424 on Linux), with the +same 325 parameter invocations. TSan adds one shared-generator function for a +Mac total of 16. New tests are also included in the exact WASI bundle inventory; +changing an expected inventory does not claim a WASI execution pass. All 49 core +cap behavior probes, pinned independent-suite policies and existing discovery +checks remain required in the full lanes. + +`--lane schema-examples-safety` supplements full safety with Release ASan (all +21 G1 functions), TSan (the one shared-generator function) and strict memory-safety +compilation of the new target and dependencies. Native Swift 6.4's observed +`--test-product JSONSchemaExamplesTests` selects the generated test product; this +does not add a public library product or change full-lane cardinalities. No +filtered lane substitutes for the full release/conformance/platform gates. + +The schema-examples consumer qualifier preserves its 15 public cases/two compiler +negatives and additionally requires ten synchronized DocC snippets in two processes. +The docs lane checks those source bytes before compilation. Exact marker/cardinality +checks reject missing documentation outcomes; all 49 baseline cap probes and the +historical G6 requirements (96 generator/515 full-root/19 sharing functions). + + +## S3 G7 integrated qualification + +The pre-composition S3 G7 exact discovery contract was 644 Mac / 628 Linux / 540 WASI functions, +325 Mac/Linux and 288 WASI parameter invocations, 45 Mac / 44 Linux shared-state +functions, and all 49 core cap probes. JSONSchemaExamples contributes 98 functions +and four sharing functions. All 126 restored maintenance identities remain required. +Earlier counts above describe their named historical increments. + +`--lane schema-examples-qualification` composes the advanced lane with the fixed +64-schema public matrix: 512 seed/profile certifications and 64 known-invalid +mutations, replayed in two processes. Full native also runs that matrix and uses +one explicit root consumer scratch path for subsequent compiler/replay checks. +A broad nonzero test count cannot satisfy any lane. Incomplete generation and +independent-suite policy exclusions remain separate from evaluated successes. + +G7 passed full default native from a Git-free extraction, full native safety, +WASI and the recorded Apple scopes. Linux preflight and Release build passed, but +the guest was interrupted for disk pressure before full tests/safety. Those gates +remain open; platform SDK compilation, simulator/device consumers and WASI do not +substitute for Linux results. See the [G7 record](../Architecture/Release1.6/G7Qualification.md). + ## Experimental composition lane `python3 Scripts/ci.py --lane composition` executes 12 composition functions in diff --git a/Documentation/DocumentationTooling.md b/Documentation/DocumentationTooling.md index 393047b..b1832f2 100644 --- a/Documentation/DocumentationTooling.md +++ b/Documentation/DocumentationTooling.md @@ -1,7 +1,7 @@ # Building and maintaining the DocC catalogs Swift source comments are the API documentation authority. Native compiler symbol -graphs supply the declarations and links for seven Swift DocC catalogs. Each catalog +graphs supply the declarations and links for eight Swift DocC catalogs. Each catalog combines the product reference with curated Topics and a focused usage guide. See the [Swift DocC documentation](https://www.swift.org/documentation/docc/). @@ -19,13 +19,21 @@ python3 Scripts/ci.py --lane docs The lane records native preflight, extracts fresh public symbol graphs, requires summaries and named-parameter documentation on every authored public declaration, checks README Swift fences against their executable consumer source, runs that -consumer, and compiles all seven DocC catalogs with warnings as errors. Zero public +consumer, and compiles all eight DocC catalogs with warnings as errors. Zero public symbols, missing comments, stale examples or a failed catalog fail the lane. +The lane also executes the standalone schema-examples consumer (15 preserved cases and two +expected compiler rejections), plus every Swift block in all five JSONSchemaExamples +guides (ten independently scoped snippets, executed in two processes). Every successful consumer value, including the +offline-reference case, receives an independent native verdict and strict +roundtrip check. Remaining work is derived from public limits and cumulative +statistics; the consumer imports no private meters. + | Output | Purpose | | --- | --- | | `.build/handoff/docc/JSON.doccarchive` | JSON API and exact values/native coding guide | | `.build/handoff/docc/JSONSchema.doccarchive` | Schema API and offline validation guide | +| `.build/handoff/docc/JSONSchemaExamples.doccarchive` | Certified candidates, conservative construction, deterministic provenance and finite lifecycle | | `.build/handoff/docc/JSONStreaming.doccarchive` | Streaming API and completion/backpressure guide | | `.build/handoff/docc/JSONHTML.doccarchive` | Exact formatted HTML API and rendering guide | | `.build/handoff/docc/JSONPatch.doccarchive` | Patch API and choosing/applying patches guide | @@ -34,9 +42,9 @@ symbols, missing comments, stale examples or a failed catalog fail the lane. | `.build/handoff/docc/symbols/` | Public compiler symbol graphs grouped by product | | `.build/handoff/docc/coverage.json` | Counts and documentation-coverage failures | | `.build/handoff/docc/receipt.json` | Commands, toolchain, counts, exits and diagnostics | -| `.build/handoff/docc/compiled-catalogs.tar.gz` | All seven compiled catalogs with their exact native filenames | +| `.build/handoff/docc/compiled-catalogs.tar.gz` | All eight compiled catalogs with their exact native filenames | | `.build/handoff/docc/compiled-catalogs.MANIFEST.sha256` | Independently verifiable hashes for every archived catalog file | -| `.build/handoff/pages/site/` | Static site, landing page and all seven references under the project base path | +| `.build/handoff/pages/site/` | Static site, landing page and all eight references under the project base path | | `.build/handoff/pages/receipt.json` | Native transformations, input/output hashes and route/asset verification | Open a `.doccarchive` in Xcode for the compiled reference. To preview a source @@ -65,7 +73,7 @@ The manifest and receipt preserve file-level and archive-level digests. The docs lane uses native `docc process-archive transform-for-static-hosting` to prepare each qualified archive under `/Swift-JSON/`. A shared landing -page links all seven references and guides. The build verifies product/guide routes, +page links all eight references and guides. The build verifies product/guide routes, local HTML links and assets, rejects private-path/credential markers and symlinks, and records a file manifest. Only the generated site is packaged for Pages; source graphs, command logs and private maintainer records are not website content. @@ -118,3 +126,25 @@ Run native and safety lanes when code changes; required branch checks also run o public documentation PRs. Preserve published source/evidence identities and create a distinct successor archive when source content changes. Performance evidence is reused only when its measured implementation and workload remain applicable. + +### Keep schema example guides executable + +Every Swift fence in `Sources/JSONSchemaExamples/Documentation.docc/*.md` is counted, +hashed and synchronized with the separate public consumer. The current contract is +ten snippets across five guides, covering exact values/keys, offline definition +selection, deterministic replay, format/default policy, remaining work and explicit +search failure. The docs/native consumer qualifiers require both process completions +and preserve all original 15 consumer cases and two compiler negatives. + +```sh +python3 -B Scripts/schema_example_documentation.py --write +python3 -B Scripts/verify_schema_example_documentation.py +swift run --package-path Examples/SchemaExamplesConsumer SchemaExamplesConsumer --documentation +``` + +Review the generated DocumentationExamples.swift diff after editing a guide. Missing, +extra, empty or foreign-import snippets and source drift fail qualification. The +`#sourceLocation` directives map compiler errors to the authored guide. The root +README has fourteen executable Swift fences in its separate seven-library consumer; +installation fragments remain manifest examples, checked by real package manifests. +Native DocC checks symbol links and the site verifies the new limits-guide route. diff --git a/Documentation/Qualification/TestDiscovery.json b/Documentation/Qualification/TestDiscovery.json index a173776..7814f17 100644 --- a/Documentation/Qualification/TestDiscovery.json +++ b/Documentation/Qualification/TestDiscovery.json @@ -10,7 +10,8 @@ "JSONHTMLTests": 97, "JSONBridgesTests": 10, "JSONCLITests": 88, - "JSONNavigationTests": 39 + "JSONNavigationTests": 39, + "JSONSchemaExamplesTests": 98 }, "parameterized_invocations": 325, "composition_external_functions": 5, @@ -31,7 +32,8 @@ "JSONHTMLTests": 5, "JSONBridgesTests": 1, "JSONCLITests": 1, - "JSONNavigationTests": 1 + "JSONNavigationTests": 1, + "JSONSchemaExamplesTests": 4 }, "coverage_unmapped": { "Sources/JSON/Pointer/JSONLocationComponent.swift": "declaration-only enum", @@ -48,6 +50,7 @@ "coverage_modules": [ "JSON", "JSONSchema", + "JSONSchemaExamples", "JSONRegexRuntime", "JSONStreaming", "JSONPatch", @@ -68,7 +71,8 @@ "JSONHTMLTests": 81, "JSONBridgesTests": 10, "JSONCLITests": 88, - "JSONNavigationTests": 39 + "JSONNavigationTests": 39, + "JSONSchemaExamplesTests": 98 }, "wasi_root_functions": { "JSONTests": 190, @@ -79,7 +83,8 @@ "JSONIntegrationTests": 32, "JSONHTMLTests": 81, "JSONBridgesTests": 10, - "JSONNavigationTests": 39 + "JSONNavigationTests": 39, + "JSONSchemaExamplesTests": 98 }, "wasi_parameterized_invocations": 288, "portable_shared_state_functions": { @@ -91,7 +96,8 @@ "JSONHTMLTests": 4, "JSONBridgesTests": 1, "JSONCLITests": 1, - "JSONNavigationTests": 1 + "JSONNavigationTests": 1, + "JSONSchemaExamplesTests": 4 }, "export_functions": { "JSONSchemaTests": 1, diff --git a/Documentation/README.md b/Documentation/README.md index 84411d4..fe03b5e 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -6,15 +6,16 @@ behavioral contracts and native qualification procedure. ## API references and guided reading -The seven candidate native DocC catalogs combine inline API documentation with focused guides: +The eight native DocC catalogs combine inline API documentation with focused guides: | Product | Catalog source | Guide | | --- | --- | --- | | JSON | [JSON](../Sources/JSON/Documentation.docc/JSON.md) | [Exact values and native coding](../Sources/JSON/Documentation.docc/ExactValuesAndCoding.md) | | JSONSchema | [JSONSchema](../Sources/JSONSchema/Documentation.docc/JSONSchema.md) | [Offline schema validation](../Sources/JSONSchema/Documentation.docc/OfflineValidation.md) | +| JSONSchemaExamples | [JSONSchemaExamples](../Sources/JSONSchemaExamples/Documentation.docc/JSONSchemaExamples.md) | [Verified candidates](../Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md) | +| JSONBridges | [JSONBridges](../Sources/JSONBridges/Documentation.docc/JSONBridges.md) | [Opt-in native conversions](../Sources/JSONBridges/Documentation.docc/NumericFidelity.md) | | JSONStreaming | [JSONStreaming](../Sources/JSONStreaming/Documentation.docc/JSONStreaming.md) | [Completion and backpressure](../Sources/JSONStreaming/Documentation.docc/CompletionAndBackpressure.md) | | JSONHTML | [JSONHTML](../Sources/JSONHTML/Documentation.docc/JSONHTML.md) | [Formatted rendering](../Sources/JSONHTML/Documentation.docc/FormattedRendering.md) | -| JSONBridges | [JSONBridges](../Sources/JSONBridges/Documentation.docc/JSONBridges.md) | [Numeric fidelity](../Sources/JSONBridges/Documentation.docc/NumericFidelity.md) | | JSONNavigation | [JSONNavigation](../Sources/JSONNavigation/Documentation.docc/JSONNavigation.md) | [Exact traversal](../Sources/JSONNavigation/Documentation.docc/Traversal.md) | | JSONPatch | [JSONPatch](../Sources/JSONPatch/Documentation.docc/JSONPatch.md) | [Choosing and applying patches](../Sources/JSONPatch/Documentation.docc/ApplyingPatches.md) | @@ -42,9 +43,10 @@ Online references track main; tagged releases include matching compiled catalogu ## Consumers, qualification and maintenance +- [Seven-library compiled example](../Examples/Consumer/README.md) +- [Schema example public API](Architecture/Release1.6/API.md), [resource scope](Architecture/Release1.6/SchemaExamplesLimits.md) and [runnable guide examples](../Examples/SchemaExamplesConsumer/README.md) - [Experimental N0/N1 source authority](Architecture/StructuralIntelligence/SOURCE-AUTHORITY.md) and [gated traversal decisions](Architecture/StructuralIntelligence/API-DECISIONS.md); JSONNavigation is not a released product. -- [Six-library compiled example](../Examples/Consumer/README.md) - [Downstream JSON/JSONSchema interface](ConsumerInterface.md) and [runnable public consumer](../Tools/MCPReadiness/README.md) - [Independent-suite policies and qualification](Qualification/README.md) - [Native CI lanes and receipts](CI/README.md) diff --git a/Examples/Consumer/Package.swift b/Examples/Consumer/Package.swift index 29ce69a..c5bb8c9 100644 --- a/Examples/Consumer/Package.swift +++ b/Examples/Consumer/Package.swift @@ -10,6 +10,7 @@ let package = Package( targets: [.executableTarget(name: "Consumer", dependencies: [ .product(name: "JSON", package: "Swift-JSON"), .product(name: "JSONSchema", package: "Swift-JSON"), + .product(name: "JSONSchemaExamples", package: "Swift-JSON"), .product(name: "JSONStreaming", package: "Swift-JSON"), .product(name: "JSONPatch", package: "Swift-JSON"), .product(name: "JSONHTML", package: "Swift-JSON"), diff --git a/Examples/Consumer/README.md b/Examples/Consumer/README.md index 6309264..429d96c 100644 --- a/Examples/Consumer/README.md +++ b/Examples/Consumer/README.md @@ -23,3 +23,8 @@ The sixth optional product, JSONBridges, is imported explicitly for the E2 numer examples. Three synchronized guide fences execute alongside all README examples. Core-only and unsupported-Float compiler rejections are checked separately by Scripts/qualify_ergonomics.py in debug and release configurations. + +The seventh library, optional JSONSchemaExamples, executes the README's native- +certified exact integer example. Adding this external consumer dependency creates +no dependency in JSON or JSONSchema. The standalone SchemaExamplesConsumer additionally +executes every S3 DocC Swift block and its native acceptance matrices. diff --git a/Examples/Consumer/Sources/Consumer/READMEExamples.swift b/Examples/Consumer/Sources/Consumer/READMEExamples.swift index 9da2562..de4f45f 100644 --- a/Examples/Consumer/Sources/Consumer/READMEExamples.swift +++ b/Examples/Consumer/Sources/Consumer/READMEExamples.swift @@ -4,6 +4,7 @@ import JSONBridges import JSON import Foundation import JSONSchema +import JSONSchemaExamples import JSONPatch import JSONStreaming import JSONHTML @@ -97,6 +98,16 @@ func runREADMEExamples() throws { let output = try result.output() precondition(output.objectValue?["valid"] == true) + let exampleSchema = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"integer","minimum":9007199254740993}"# + )) + let exampleGenerator = try JSONSchemaExampleGenerator(document: exampleSchema) + let schemaExample = try exampleGenerator.generate(seed: 42, profile: .minimal) + let exampleMinimum = try JSONNumber(parsing: "9007199254740993") + precondition(schemaExample.validation.isValid) + precondition(schemaExample.value.numberValue == exampleMinimum) + precondition(schemaExample.statistics[.evaluationWorkUnits] > 0) + let patch = try JSONPatch(operations: [ .replace(path: JSONPointer(parsing: "/count"), value: 42) ]) diff --git a/Examples/SchemaExamplesConsumer/CompileNegative/CertificateIsNotConstructible.swift b/Examples/SchemaExamplesConsumer/CompileNegative/CertificateIsNotConstructible.swift new file mode 100644 index 0000000..09a7d55 --- /dev/null +++ b/Examples/SchemaExamplesConsumer/CompileNegative/CertificateIsNotConstructible.swift @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSONSchemaExamples +func forbidden() { _ = JSONSchemaExampleResult() } diff --git a/Examples/SchemaExamplesConsumer/CompileNegative/PlanIsPrivate.swift b/Examples/SchemaExamplesConsumer/CompileNegative/PlanIsPrivate.swift new file mode 100644 index 0000000..874387b --- /dev/null +++ b/Examples/SchemaExamplesConsumer/CompileNegative/PlanIsPrivate.swift @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSONSchema +func forbidden(_ schema: CompiledJSONSchema) { _ = schema.exampleRoot } diff --git a/Examples/SchemaExamplesConsumer/Package.swift b/Examples/SchemaExamplesConsumer/Package.swift new file mode 100644 index 0000000..71e42f9 --- /dev/null +++ b/Examples/SchemaExamplesConsumer/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version: 6.4 +// SPDX-License-Identifier: Apache-2.0 +import PackageDescription + +let package = Package( + name: "SchemaExamplesConsumer", + platforms: [.macOS("27.0"), .iOS("27.0")], + dependencies: [.package(name: "Swift-JSON", path: "../..")], + targets: [.executableTarget(name: "SchemaExamplesConsumer", dependencies: [ + .product(name: "JSON", package: "Swift-JSON"), + .product(name: "JSONSchema", package: "Swift-JSON"), + .product(name: "JSONSchemaExamples", package: "Swift-JSON") + ])], + swiftLanguageModes: [.v6] +) diff --git a/Examples/SchemaExamplesConsumer/README.md b/Examples/SchemaExamplesConsumer/README.md new file mode 100644 index 0000000..814c524 --- /dev/null +++ b/Examples/SchemaExamplesConsumer/README.md @@ -0,0 +1,78 @@ +# Schema examples public consumer + +This standalone Swift 6.4 package imports only the public JSON, JSONSchema and +JSONSchemaExamples products. It executes 15 cases: strict/exact values, scalar-key +identity, preceding invalid candidates, static/offline references, explicit proof +and incomplete-search failures. It independently validates every success and +roundtrips exact JSON through the strict writer/parser. It uses no private meters. + +```sh +swift run --package-path Examples/SchemaExamplesConsumer SchemaExamplesConsumer +``` + +The two compile-negative fixtures must fail for package access protection and an +inaccessible certificate initializer. `Scripts/qualify_schema_examples.py` builds +the real consumer and typechecks them against its actual modules. Construction remains conservative and natively certified. See the G3/G4 architecture records +and JSONSchemaExamples DocC guide for the current boundary. + +The two true-schema profiles emit `G2_VECTORS` (the retained qualification marker) for the independent Python stdlib +transport oracle. The qualifier executes the binary in a second process and +requires identical value/trace bytes. This is native process repeatability; +Linux/WASI determinism remains a later platform gate. The consumer uses public +corpus, policy and trace identities and no private meters. + + +Run the foundational acceptance matrix from the repository root after building: + +```sh +python3 -B Scripts/ci.py --lane schema-examples-construction +``` + +The separate public G3 mode evaluates 24 must-generate schemas across two profiles +and four seeds (192 completed native certifications per process), 90 fixture +witnesses and two sound proofs. Its qualifier repeats in a second process and +materializes every value/trace/work row. The 18 G4 cases remain outside these +success counts. Fixture hashes and the G0 numeric correction are explicit. + + +Run the advanced acceptance layer after the current public consumer build: + +```sh +python3 -B Scripts/ci.py --lane schema-examples-advanced +``` + +This retains the foundational checks and adds 10 G4 must-generate schemas and +8 may-fail schemas, both profiles and four seeds (144 invocations per process). +Every success is certified, independently validated and strictly roundtripped. +Only explicit unsupported/searchExhausted results are accepted for may-fail cases; +no proof, configuration error, native operational error or cancellation can replace +a required success. The qualifier repeats in two processes and materializes each +outcome, plus native verdicts for all 44 schemas/155 witness expectations. Failures +carry no invented value or successful work report. General inversion, triggered +dependency solving and constructive dynamic scope remain incomplete. + +## Compiled public documentation + +All ten Swift snippets across the five JSONSchemaExamples DocC guides execute as +independent scopes in DocumentationExamples.swift, synchronized byte-for-byte from +the authored guides. The exact examples cover scalar-distinct required/null keys, +large integers, offline resource/definition selection and native revalidation, +contains/pattern proposals, seeded replay, remaining work, unchanged format/default +policy and honest incomplete search. No package-private state is imported. + +```sh +python3 -B Scripts/schema_example_documentation.py +swift run --package-path Examples/SchemaExamplesConsumer SchemaExamplesConsumer --documentation +``` + +The existing qualifier runs this mode twice and requires exactly ten completed +snippets each time, in addition to the preserved 15 cases, two process repeatability +vectors, independent transport oracle and two compiler negatives. Drift and count +failures are rejected, without adding root package dependencies. See the current +[API](../../Documentation/Architecture/Release1.6/API.md), +[49-metric scope](../../Documentation/Architecture/Release1.6/SchemaExamplesLimits.md) +and [compiled policy guide](../../Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md). + +JSONSchemaDSL is absent from this S3 source snapshot. Its independent train remains +untouched; a DSL/handwritten equality example waits for an available integrated +product. Native examples do not establish later Linux/WASI/mobile release gates. diff --git a/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/DocumentationExamples.swift b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/DocumentationExamples.swift new file mode 100644 index 0000000..d995990 --- /dev/null +++ b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/DocumentationExamples.swift @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 +// Generated from every JSONSchemaExamples DocC Swift block; do not edit by hand. +import JSON +import JSONSchema +import JSONSchemaExamples + +func runSchemaExampleDocumentation() throws -> Int { + var completed = 0 + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/AdvancedConstruction.md", line: 6) + let document = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"array","minItems":2,"uniqueItems":true,"items":{"type":"string"},"contains":{"pattern":"^SKU-[0-9]{4}$"},"minContains":2,"maxContains":2}"# + )) + let result = try JSONSchemaExampleGenerator(document: document).generate(seed: 42) + precondition(result.validation.isValid) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/DeterministicConstruction.md", line: 8) + let corpus = JSONSchemaExampleCorpus(values: [.string("example"), .null]) + let prepared = try JSONSchemaExampleGenerator( + document: JSONSchemaDocument(JSONParser().parse("true")), + options: .init(corpus: corpus) + ) + let result = try prepared.generate(seed: 42, profile: .demonstration) + precondition(result.validation.isValid) + precondition(result.corpusDigest == prepared.corpusDigest) + precondition(result.randomDraws > 0) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/DeterministicConstruction.md", line: 73) + let prepared = try JSONSchemaExampleGenerator( + document: JSONSchemaDocument(JSONParser().parse(#"{"type":"string","minLength":3}"#)) + ) + let first = try prepared.generate(seed: 42, profile: .demonstration) + let second = try prepared.generate(seed: 42, profile: .demonstration) + precondition(first.value == second.value && first.traceDigest == second.traceDigest) + precondition(first.statistics == second.statistics) + precondition(first.sourceSnapshotDigest == prepared.sourceSnapshotDigest) + precondition(first.policyDigest == prepared.policyDigest && first.corpusDigest == prepared.corpusDigest) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md", line: 14) + var limits = JSONLimits.conservative + limits[.workUnits] = 2_000_000 + var evaluation = JSONSchemaOptions(limits: limits) + evaluation.collectAnnotations = true + let options = JSONSchemaExampleOptions( + limits: limits, maximumAttempts: 4, validationOptions: evaluation + ) + let prepared = try JSONSchemaExampleGenerator( + document: JSONSchemaDocument(JSONParser().parse(#"{"enum":[0,"ok"],"type":"string"}"#)), + options: options + ) + let result = try prepared.generate() + let ceiling = min(prepared.options.limits[.workUnits], + prepared.options.validationOptions.limits[.workUnits]) + let used = result.statistics[.workUnits] + precondition(used <= ceiling) + let remainingWork = ceiling - used + precondition(remainingWork < ceiling && result.attempts == 2) + precondition(result.statistics[.evaluationWorkUnits] > 0) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md", line: 57) + let document = try JSONSchemaDocument(JSONParser().parse( + #"{"const":"not-an-email","format":"email","default":"example@example.com"}"# + )) + let annotated = try JSONSchemaExampleGenerator(document: document).generate() + precondition(annotated.value == .string("not-an-email")) + precondition(!annotated.validationOptions.formatAssertion) + let plan = try JSONSchemaCompiler().compile(document) + let asserted = try plan.validate(annotated.value, options: .init(formatAssertion: true)) + precondition(!asserted.isValid) + precondition(document.raw.objectValue?["default"] == .string("example@example.com")) + precondition(!annotated.compilationOptions.formatAssertion) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md", line: 6) + let document = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"integer","minimum":9007199254740993}"# + )) + let generator = try JSONSchemaExampleGenerator(document: document) + let result = try generator.generate(seed: 42) + precondition(result.validation.isValid) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md", line: 80) + let document = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"object","required":["é","e\u0301"],"properties":{"é":{"const":9007199254740993},"e\u0301":{"const":null},"optional":{"default":42}}}"# + )) + let result = try JSONSchemaExampleGenerator(document: document).generate() + let object = result.value.objectValue + let exact = try JSONNumber(parsing: "9007199254740993") + precondition(object?.count == 2 && object?["é"]?.numberValue == exact) + precondition(object?["e\u{301}"] == .null && object?["optional"] == nil) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md", line: 12) + let document = try JSONSchemaDocument(JSONParser().parse( + #"{"const":9007199254740993}"# + )) + let prepared = try JSONSchemaExampleGenerator(document: document) + let result = try prepared.generate(seed: 42, profile: .minimal) + let text = try JSONWriter().writeString(result.value) + precondition(result.validation.isValid && text == "9007199254740993") + precondition(result.attempts == 1 && result.statistics[.evaluationWorkUnits] > 0) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md", line: 40) + let parser = JSONParser() + let resource = try JSONSchemaDocument(parser.parse( + #"{"$defs":{"count":{"type":"integer","minimum":9007199254740993}}}"# + ), retrievalURI: JSONSchemaURI("https://example.com/schemas/counts")) + let registry = try JSONSchemaRegistry(documents: [resource]) + let root = try JSONSchemaDocument(parser.parse( + ##"{"$ref":"https://example.com/schemas/counts#/$defs/count"}"## + )) + let prepared = try JSONSchemaExampleGenerator(document: root, registry: registry) + let result = try prepared.generate() + let plan = try JSONSchemaCompiler().compile(root, registry: registry) + let independentlyValid = try plan.validate(result.value).isValid + let exact = try JSONNumber(parsing: "9007199254740993") + let roundtrip = try JSONParser().parse(JSONWriter().write(result.value)) + precondition(independentlyValid && result.value.numberValue == exact) + precondition(roundtrip == result.value) + #sourceLocation() + completed += 1 + } + do { + #sourceLocation(file: "Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md", line: 72) + let document = try JSONSchemaDocument(JSONParser().parse( + #"{"const":1,"type":"string"}"# + )) + let prepared = try JSONSchemaExampleGenerator(document: document) + var exhausted = false + do { + _ = try prepared.generate() + } catch JSONSchemaExampleFailure.searchExhausted(let attempts) { + precondition(attempts == 1) + exhausted = true + } + precondition(exhausted) + #sourceLocation() + completed += 1 + } + precondition(completed == 10) + return completed +} diff --git a/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G3Acceptance.swift b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G3Acceptance.swift new file mode 100644 index 0000000..7f6eb6b --- /dev/null +++ b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G3Acceptance.swift @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONSchema +import JSONSchemaExamples + +private enum G3AcceptanceFailure: Error { case mismatch } +enum G3Acceptance { + static func run(fixturePath: String, manifestPath: String) throws { + // Caller-owned qualification I/O and assertions are separate from the + // generator's one invocation ledger. No private plan or meter is imported. + let data = try JSONParser().parse(Data(contentsOf: URL(fileURLWithPath: fixturePath))) + let manifest = try JSONParser().parse(Data(contentsOf: URL(fileURLWithPath: manifestPath))) + guard let cases = data.objectValue?["cases"]?.arrayValue, + let expectedIDs = manifest.objectValue?["g3_must_generate_ids"]?.arrayValue else { throw G3AcceptanceFailure.mismatch } + var observed: [JSON] = [], results: [JSON] = [], proofs: [JSON] = [], witnessCount = 0 + for value in cases { + guard let c = value.objectValue, c["acceptanceGate"] == .string("G3"), + let id = c["id"]?.stringValue, let raw = c["schema"] else { continue } + let assertion = c["validation"]?.objectValue?["formatAssertion"]?.booleanValue == true + let options = JSONSchemaExampleOptions(validationOptions: .init(formatAssertion: assertion)) + let generator = try JSONSchemaExampleGenerator(document: .init(raw), options: options) + if c["generatorExpectation"] == .string("must-prove-unsatisfiable") { + do { _ = try generator.generate(); throw G3AcceptanceFailure.mismatch } + catch JSONSchemaExampleFailure.provenUnsatisfiable(let proof) { + guard id == "boolean-false" && proof == .falseSchema || id == "not-true" && proof == .notTrue else { throw G3AcceptanceFailure.mismatch } + proofs.append(.string(id)); continue + } + } + guard c["generatorExpectation"] == .string("must-generate") else { throw G3AcceptanceFailure.mismatch } + let plan = try JSONSchemaCompiler(options: options.compilationOptions).compile(generator.document, registry: generator.registry) + let validator = JSONSchemaValidator(options: options.validationOptions) + guard let witnesses = c["witnesses"]?.arrayValue else { throw G3AcceptanceFailure.mismatch } + for witness in witnesses { + guard let instance = witness.objectValue?["instance"], let expected = witness.objectValue?["valid"]?.booleanValue, + try validator.validate(instance, against: plan).isValid == expected else { throw G3AcceptanceFailure.mismatch } + witnessCount += 1 + } + for (name, profile) in [("minimal", JSONSchemaExampleProfile.minimal), ("demonstration", .demonstration)] { + for seed in [UInt64(0), 1, 42, UInt64.max] { + let result = try generator.generate(seed: seed, profile: profile) + let bytes = try JSONWriter(options: .init(keyOrder: .scalar, numberStyle: .normalized)).write(result.value) + let roundtrip = try JSONParser().parse(bytes) + let ceiling = min(options.limits[.workUnits], options.validationOptions.limits[.workUnits]) + guard result.validation.isValid, result.validation.diagnosticsComplete, + try validator.validate(result.value, against: plan).isValid, + roundtrip == result.value, result.statistics[.workUnits] <= ceiling, + result.statistics[.evaluationWorkUnits] > 0 else { throw G3AcceptanceFailure.mismatch } + let row = try JSONObject(members: [ + .init("id", value: .string(id)), .init("profile", value: .string(name)), + .init("seed", value: .number(JSONNumber(integer: seed))), + .init("value", value: result.value), .init("trace", value: .string(result.traceDigest)), + .init("snapshot", value: .string(result.sourceSnapshotDigest)), + .init("corpus", value: .string(result.corpusDigest)), .init("policy", value: .string(result.policyDigest)), + .init("attempts", value: .number(JSONNumber(integer: result.attempts))), + .init("work", value: .number(JSONNumber(integer: result.statistics[.workUnits]))), + .init("evaluationWork", value: .number(JSONNumber(integer: result.statistics[.evaluationWorkUnits]))), + .init("remainingWork", value: .number(JSONNumber(integer: ceiling - result.statistics[.workUnits]))), + .init("peakLogicalRetainedBytes", value: .number(JSONNumber(integer: result.statistics.peakLogicalRetainedBytes))), + .init("nativeCertified", value: true), .init("independentNativeValid", value: true), .init("roundtrip", value: true) + ]) + results.append(.object(row)) + } + } + observed.append(.string(id)) + } + guard observed == expectedIDs, observed.count == 24, results.count == 192, proofs.count == 2, + witnessCount == 90 else { throw G3AcceptanceFailure.mismatch } + let report = try JSONObject(members: [ + .init("algorithm", value: .string("s3-candidates-v4")), .init("ids", value: .array(observed)), + .init("results", value: .array(results)), .init("proofs", value: .array(proofs)), .init("witnesses", value: 90) + ]) + print("G3_RECEIPT " + (try JSONWriter().writeString(.object(report)))) + print("SCHEMA_EXAMPLES_G3 passed cases=24 profiles=2 seeds=4 validated=192 witnesses=90 proofs=2") + } +} diff --git a/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G4Acceptance.swift b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G4Acceptance.swift new file mode 100644 index 0000000..1e791f0 --- /dev/null +++ b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G4Acceptance.swift @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONSchema +import JSONSchemaExamples + +private enum G4AcceptanceFailure: Error { case mismatch } +enum G4Acceptance { + static func run(fixturePath: String, manifestPath: String) throws { + // Caller-owned qualification I/O and native oracle calls do not claim + // instruction or memory caps from the generator's own public reports. + let input = try JSONParser().parse(Data(contentsOf: URL(fileURLWithPath: fixturePath))) + let manifest = try JSONParser().parse(Data(contentsOf: URL(fileURLWithPath: manifestPath))) + guard let cases = input.objectValue?["cases"]?.arrayValue, + let requiredIDs = manifest.objectValue?["g4_must_generate_ids"]?.arrayValue, + let incompleteIDs = manifest.objectValue?["g4_may_fail_ids"]?.arrayValue else { throw G4AcceptanceFailure.mismatch } + var results: [JSON] = [], observedRequired: [JSON] = [], observedIncomplete: [JSON] = [] + var witnesses = 0, schemas = 0, mustValid = 0, mayValid = 0, explicit = 0 + for value in cases { + guard let c = value.objectValue, let raw = c["schema"], let id = c["id"]?.stringValue, + let witnessRows = c["witnesses"]?.arrayValue else { throw G4AcceptanceFailure.mismatch } + let options = JSONSchemaExampleOptions(validationOptions: .init(formatAssertion: c["validation"]?.objectValue?["formatAssertion"]?.booleanValue == true)) + var documents: [JSONSchemaDocument] = [] + if let resources = c["resources"]?.objectValue { + for member in resources { documents.append(try .init(member.value, retrievalURI: JSONSchemaURI(member.key.string))) } + } + let document = try JSONSchemaDocument(raw), registry = try JSONSchemaRegistry(documents: documents) + let plan = try JSONSchemaCompiler(options: options.compilationOptions).compile(document, registry: registry) + let validator = JSONSchemaValidator(options: options.validationOptions) + // Evaluate every witness in all 44 cases, including the six negatives + // attached to the two trivial G3 proof cases. These are native checks. + for witness in witnessRows { + guard let instance = witness.objectValue?["instance"], let expected = witness.objectValue?["valid"]?.booleanValue, + try validator.validate(instance, against: plan).isValid == expected else { throw G4AcceptanceFailure.mismatch } + witnesses += 1 + } + schemas += 1 + guard c["acceptanceGate"] == .string("G4") else { continue } + let must = c["generatorExpectation"] == .string("must-generate") + guard must || c["generatorExpectation"] == .string("may-fail-explicitly") else { throw G4AcceptanceFailure.mismatch } + let generator = try JSONSchemaExampleGenerator(document: document, registry: registry, options: options) + for (name, profile) in [("minimal",JSONSchemaExampleProfile.minimal),("demonstration",.demonstration)] { + for seed in [UInt64(0),1,42,UInt64.max] { + var row: [JSONMember] = [.init("id",value: .string(id)),.init("profile",value: .string(name)), + .init("seed",value: .number(JSONNumber(integer: seed))),.init("required",value: .boolean(must))] + do { + let result = try generator.generate(seed: seed, profile: profile) + let bytes = try JSONWriter(options: .init(keyOrder: .scalar, numberStyle: .normalized)).write(result.value) + let ceiling = min(options.limits[.workUnits], options.validationOptions.limits[.workUnits]) + guard result.validation.isValid, result.validation.diagnosticsComplete, + try validator.validate(result.value, against: plan).isValid, + try JSONParser().parse(bytes) == result.value, + result.statistics[.workUnits] <= ceiling else { throw G4AcceptanceFailure.mismatch } + row += [.init("outcome",value: .string("validated")),.init("value",value: result.value), + .init("nativeCertified",value: true),.init("independentNativeValid",value: true),.init("roundtrip",value: true), + .init("trace",value: .string(result.traceDigest)),.init("snapshot",value: .string(result.sourceSnapshotDigest)), + .init("corpus",value: .string(result.corpusDigest)),.init("policy",value: .string(result.policyDigest)), + .init("work",value: .number(JSONNumber(integer: result.statistics[.workUnits]))), + .init("evaluationWork",value: .number(JSONNumber(integer: result.statistics[.evaluationWorkUnits]))), + .init("remainingWork",value: .number(JSONNumber(integer: ceiling - result.statistics[.workUnits]))), + .init("peakLogicalRetainedBytes",value: .number(JSONNumber(integer: result.statistics.peakLogicalRetainedBytes))), + .init("attempts",value: .number(JSONNumber(integer: result.attempts)))] + if must { mustValid += 1 } else { mayValid += 1 } + } catch let failure as JSONSchemaExampleFailure { + guard !must else { throw failure } + switch failure { + case .unsupportedFeature(let feature): + let code: String + switch feature { + case .candidateConstruction: code = "candidateConstruction" + case .dynamicReference: code = "dynamicReference" + case .cyclicReference: code = "cyclicReference" + } + row += [.init("outcome",value: .string("unsupported")),.init("reason",value: .string(code))] + case .searchExhausted(let attempts): + guard attempts > 0 && attempts <= options.maximumAttempts else { throw G4AcceptanceFailure.mismatch } + row += [.init("outcome",value: .string("searchExhausted")),.init("attempts",value: .number(JSONNumber(integer: attempts)))] + default: throw G4AcceptanceFailure.mismatch + } + // No value or successful statistics are invented after error. + explicit += 1 + } + // Core operational errors/cancellation propagate and fail acceptance. + results.append(.object(try JSONObject(members: row))) + } + } + if must { observedRequired.append(.string(id)) } else { observedIncomplete.append(.string(id)) } + } + guard schemas == 44, witnesses == 155, observedRequired == requiredIDs, observedIncomplete == incompleteIDs, + observedRequired.count == 10, observedIncomplete.count == 8, mustValid == 80, + mayValid + explicit == 64, results.count == 144 else { throw G4AcceptanceFailure.mismatch } + let report = try JSONObject(members: [ + .init("algorithm",value: .string("s3-candidates-v4")),.init("requiredIDs",value: .array(observedRequired)), + .init("incompleteIDs",value: .array(observedIncomplete)),.init("results",value: .array(results)), + .init("schemas",value: 44),.init("witnesses",value: 155),.init("mustValid",value: 80), + .init("mayValid",value: .number(JSONNumber(integer: mayValid))),.init("explicit",value: .number(JSONNumber(integer: explicit))) + ]) + print("G4_RECEIPT " + (try JSONWriter().writeString(.object(report)))) + print("SCHEMA_EXAMPLES_G4 passed required=10 incomplete=8 profiles=2 seeds=4 invocations=144 witnesses=155") + } +} diff --git a/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G7Qualification.swift b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G7Qualification.swift new file mode 100644 index 0000000..af2131e --- /dev/null +++ b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/G7Qualification.swift @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONSchema +import JSONSchemaExamples + +private enum G7Failure: Error { case mismatch } +enum G7Qualification { + static func run(path: String) throws { + let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path)) + let bytes = try handle.readToEnd() ?? Data() + try handle.close() + guard let cases = try JSONParser().parse(bytes).objectValue?["cases"]?.arrayValue, + cases.count == 64 else { throw G7Failure.mismatch } + var count = 0 + for row in cases { + guard let id = row.objectValue?["id"], let raw = row.objectValue?["schema"], + let bad = row.objectValue?["invalid"] else { throw G7Failure.mismatch } + let generator = try JSONSchemaExampleGenerator(document: .init(raw)) + let independent = try JSONSchemaCompiler().compile(generator.document) + guard try !independent.validate(bad).isValid else { throw G7Failure.mismatch } + for (name, profile) in [("minimal", JSONSchemaExampleProfile.minimal), ("demonstration", .demonstration)] { + for seed in [UInt64(0), 1, 42, UInt64.max] { + let result = try generator.generate(seed: seed, profile: profile) + guard result.validation.isValid, result.validation.diagnosticsComplete, + try independent.validate(result.value).isValid, + result.statistics[.evaluationWorkUnits] > 0, + try JSONParser().parse(JSONWriter().write(result.value)) == result.value else { throw G7Failure.mismatch } + let output = try JSONObject(members: [ + .init("id", value: id), .init("profile", value: .string(name)), + .init("seed", value: .number(JSONNumber(integer: seed))), + .init("value", value: result.value), .init("trace", value: .string(result.traceDigest)), + .init("work", value: .number(JSONNumber(integer: result.statistics[.workUnits]))), + .init("evaluationWork", value: .number(JSONNumber(integer: result.statistics[.evaluationWorkUnits]))), + .init("attempts", value: .number(JSONNumber(integer: result.attempts)))]) + print("G7_RESULT " + String(decoding: try JSONWriter(options: .init(keyOrder: .scalar, numberStyle: .normalized)).write(.object(output)), as: UTF8.self)) + count += 1 + } + } + } + guard count == 512 else { throw G7Failure.mismatch } + print("SCHEMA_EXAMPLES_G7 passed schemas=64 certified=512 invalid=64") + } +} diff --git a/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/main.swift b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/main.swift new file mode 100644 index 0000000..a578f66 --- /dev/null +++ b/Examples/SchemaExamplesConsumer/Sources/SchemaExamplesConsumer/main.swift @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema +import JSONSchemaExamples + +private enum ConsumerFailure: Error { case mismatch(Int) } +private func schema(_ text: String, uri: String? = nil) throws -> JSONSchemaDocument { + try JSONSchemaDocument(JSONParser().parse(text), retrievalURI: uri.map { try JSONSchemaURI($0) }) +} + +@main struct SchemaExamplesConsumer { + static func main() throws { + if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--g7-fuzz" { + try G7Qualification.run(path: CommandLine.arguments[2]); return + } + if CommandLine.arguments.count == 2, CommandLine.arguments[1] == "--documentation" { + let completed = try runSchemaExampleDocumentation() + print("SCHEMA_EXAMPLES_DOCUMENTATION passed snippets=\(completed)") + return + } + if CommandLine.arguments.count == 5, CommandLine.arguments[1] == "--g4-fixtures", CommandLine.arguments[3] == "--g4-manifest" { + try G4Acceptance.run(fixturePath: CommandLine.arguments[2], manifestPath: CommandLine.arguments[4]); return + } + if CommandLine.arguments.count == 5, CommandLine.arguments[1] == "--g3-fixtures", CommandLine.arguments[3] == "--g3-manifest" { + try G3Acceptance.run(fixturePath: CommandLine.arguments[2], manifestPath: CommandLine.arguments[4]); return + } + let samples: [(String, String, UInt64)] = [ + ("true", "null", 1), + ("{}", "null", 1), + (#"{"const":null}"#, "null", 1), + (#"{"const":9007199254740993}"#, "9007199254740993", 1), + (#"{"const":9007199254740994.5}"#, "9007199254740994.5", 1), + (#"{"const":{"é":1,"e\u0301":2}}"#, #"{"é":1,"e\u0301":2}"#, 1), + (#"{"enum":[1,"ok"],"type":"string"}"#, #""ok""#, 2), + (#"{"default":1,"examples":["ok"],"type":"string"}"#, #""ok""#, 2), + (##"{"$defs":{"x":{"const":7}},"$ref":"#/$defs/x"}"##, "7", 1) + ] + var passed = 0 + for (source, expected, attempts) in samples { + let document = try schema(source) + let generator = try JSONSchemaExampleGenerator(document: document) + let result = try generator.generate(seed: 42) + let expectedValue = try JSONParser().parse(expected) + let native = try JSONSchemaCompiler().compile(document).validate(result.value) + let roundtrip = try JSONParser().parse(JSONWriter().write(result.value)) + let workCeiling = min(generator.options.limits[.workUnits], generator.options.validationOptions.limits[.workUnits]) + guard result.value == expectedValue, result.validation.isValid, + result.attempts == attempts, result.seed == 42, + result.statistics[.evaluationWorkUnits] > 0, + result.sourceSnapshotDigest == generator.sourceSnapshotDigest, + native.isValid, roundtrip == result.value, + result.statistics[.workUnits] <= workCeiling else { throw ConsumerFailure.mismatch(passed) } + let remainingWork = workCeiling - result.statistics[.workUnits] + guard remainingWork < workCeiling else { throw ConsumerFailure.mismatch(passed) } + passed += 1 + } + let registry = try JSONSchemaRegistry(documents: [schema(#"{"const":"offline"}"#, uri: "https://e.test/value")]) + let prepared = try JSONSchemaExampleGenerator(document: schema(#"{"$ref":"https://e.test/value"}"#), registry: registry) + let offline = try prepared.generate() + let offlinePlan = try JSONSchemaCompiler().compile(prepared.document, registry: registry) + let offlineValid = try offlinePlan.validate(offline.value).isValid + let offlineRoundtrip = try JSONParser().parse(JSONWriter().write(offline.value)) + guard offline.value == .string("offline"), offline.validation.isValid, + offlineValid, offlineRoundtrip == offline.value else { throw ConsumerFailure.mismatch(passed) } + passed += 1 + do { + _ = try JSONSchemaExampleGenerator(document: schema("false")).generate() + throw ConsumerFailure.mismatch(passed) + } catch JSONSchemaExampleFailure.provenUnsatisfiable(.falseSchema) { passed += 1 } + do { + _ = try JSONSchemaExampleGenerator(document: schema(#"{"const":1,"type":"string"}"#)).generate() + throw ConsumerFailure.mismatch(passed) + } catch JSONSchemaExampleFailure.searchExhausted(attempts: 1) { passed += 1 } + let vectorGenerator = try JSONSchemaExampleGenerator(document: schema("true")) + var vectorRows: [String] = [] + for (name, profile) in [("minimal", JSONSchemaExampleProfile.minimal), ("demonstration", .demonstration)] { + let result = try vectorGenerator.generate(seed: 42, profile: profile) + let bytes = try JSONWriter().write(result.value) + let hex = bytes.map { String($0, radix: 16).count == 1 ? "0" + String($0, radix: 16) : String($0, radix: 16) }.joined() + guard result.validation.isValid, result.corpusDigest == vectorGenerator.corpusDigest, + result.policyDigest == vectorGenerator.policyDigest, + result.generatorVersion == "s3-candidates-v4", result.rngAlgorithm == "splitmix64-v1", + result.corpusDigestAlgorithm == "swift-json-example-corpus-v1", + result.policyDigestAlgorithm == "swift-json-example-policy-v1", + result.traceDigestAlgorithm == "swift-json-example-trace-v2", + result.schemaDigestAlgorithm == "swift-json-schema-snapshot-v1", + try JSONSchemaCompiler().compile(vectorGenerator.document).validate(result.value).isValid else { throw ConsumerFailure.mismatch(passed) } + vectorRows.append("\"\(name)\":{\"trace\":\"\(result.traceDigest)\",\"events\":\(result.traceEventCount),\"draws\":\(result.randomDraws),\"value_hex\":\"\(hex)\",\"trace_bytes\":\(result.statistics[.diagnosticBytes])}") + passed += 1 + } + print("G2_VECTORS {\"snapshot\":\"\(vectorGenerator.sourceSnapshotDigest)\",\"corpus\":\"\(vectorGenerator.corpusDigest)\",\"policy\":\"\(vectorGenerator.policyDigest)\",\(vectorRows.joined(separator: ","))}") + let exactCorpus = JSONSchemaExampleCorpus(values: [try JSONParser().parse(#"{"é":9007199254740993,"e\u0301":9007199254740994.5}"#)]) + let exactGenerator = try JSONSchemaExampleGenerator(document: schema("true"), options: .init(corpus: exactCorpus)) + let exact = try exactGenerator.generate(seed: 42, profile: .demonstration) + guard exact.value == exactCorpus.values[0], exact.value.objectValue?.count == 2, + exact.randomDraws == 1, exact.validation.isValid else { throw ConsumerFailure.mismatch(passed) } + passed += 1 + guard passed == 15 else { throw ConsumerFailure.mismatch(passed) } + print("SCHEMA_EXAMPLES_CONSUMER passed cases=15") + } +} diff --git a/Package.swift b/Package.swift index 4e78f36..6e9d455 100644 --- a/Package.swift +++ b/Package.swift @@ -8,6 +8,7 @@ let package = Package( products: [ .library(name: "JSON", targets: ["JSON"]), .library(name: "JSONSchema", targets: ["JSONSchema"]), + .library(name: "JSONSchemaExamples", targets: ["JSONSchemaExamples"]), .library(name: "JSONStreaming", targets: ["JSONStreaming"]), .library(name: "JSONPatch", targets: ["JSONPatch"]), .library(name: "JSONHTML", targets: ["JSONHTML"]), @@ -25,6 +26,8 @@ let package = Package( .testTarget(name: "JSONBridgesTests", dependencies: ["JSONBridges", "JSON"]), .target(name: "JSONRegexRuntime", dependencies: ["JSON"]), .target(name: "JSONSchema", dependencies: ["JSON", "JSONRegexRuntime"], resources: [.copy("Resources/Draft2020-12")]), + .target(name: "JSONSchemaExamples", dependencies: ["JSON", "JSONSchema"]), + .testTarget(name: "JSONSchemaExamplesTests", dependencies: ["JSONSchemaExamples", "JSON", "JSONSchema"]), .target(name: "JSONStreaming", dependencies: ["JSON"]), .target(name: "JSONPatch", dependencies: ["JSON"]), .target(name: "JSONHTML", dependencies: ["JSON"], exclude: ["Assets"]), diff --git a/README.md b/README.md index b14fc75..99b5ac1 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ Maintained by [Thorsten Fuchs](https://github.com/ThorFuchs). Swift-JSON follows semantic versioning. Use a released version and retain `Package.resolved` for reproducible builds; `main` tracks ongoing development. -This feature branch also contains an [opt-in structural traversal product candidate](Documentation/Architecture/StructuralIntelligence/N1Promotion.md). -It preserves the seven released products and proposes JSONNavigation as the eighth. -Merge and release remain human-gated; the [compiled traversal guide](Sources/JSONNavigation/Documentation.docc/Traversal.md) +This branch combines main’s bounded composition and opt-in structural traversal with +the proposed JSONSchemaExamples product: eight libraries and the CLI. +Release remains human-gated; the [compiled traversal guide](Sources/JSONNavigation/Documentation.docc/Traversal.md) describes its many-node scope, exact locations, finite accounting and coverage. Published **1.5.0** provides six libraries and the CLI. This development branch @@ -73,6 +73,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 | +| **JSONSchemaExamples** | 1.6 development: deterministic exact candidates, conservative typed construction and native schema certification | | **JSONStreaming** | Pull syntax events and values, strict NDJSON, root-array elements and awaited output chunks | | **JSONHTML** | Exact HTML and immutable exports since 1.3.0; structured forms/cards and bounded previews on unreleased main | | **swift-json** | Bounded command-line JSON workflows (available since 1.5.0) | @@ -85,7 +86,16 @@ Choose the products your application needs: SwiftPM dependency graph is empty. Qualification and benchmark tools are separate packages. -Linux supports the six libraries and CLI with portable schema integrity +The optional 1.6 development product `JSONSchemaExamples` depends only on JSON and +JSONSchema. It prepares immutable offline resources and offers exact declared hints +and conservative typed candidates. Every returned value has passed native validation +against the original root. The supported subset includes exact numeric/string bounds, +required objects, positional arrays, static references, composition hints, contains +minima and bounded anchored ASCII-pattern proposals. Unsupported or exhausted search +is explicit and does not establish unsatisfiability. See [generate verified examples](#generate-verified-schema-examples-16-development) +and the [compiled public guide consumer](Examples/SchemaExamplesConsumer/README.md). + +The inherited Linux increment adds portable schema integrity for its qualified products without runtime dependencies. Its initial qualification scope is Swift 6.4+, 64-bit Ubuntu 24.04/glibc; see the [Linux contract](Documentation/Architecture/Linux.md) for execution gates and limits. Apple platform requirements remain unchanged. @@ -94,7 +104,9 @@ The separately qualified WASI core scope targets full-language Swift 6.4 `wasm32-unknown-wasip1`, with finite defaults and exact persistence. Its [platform contract](Documentation/Architecture/WebAssembly.md) documents resource packaging, execution hosts, qualification scope and safety limitations. Browser -and Embedded Swift hosting require separate qualification. +and Embedded Swift hosting require separate qualification. JSONSchemaExamples has native +source-bound G1–G7 and caller-lifetime repair evidence. Fresh combined-source +qualification is tracked in [G8 integration](Documentation/Architecture/Release1.6/G8Integration.md). ## Installation @@ -113,7 +125,7 @@ Then add products to your target dependencies: Add `JSONStreaming`, `JSONPatch` and `JSONHTML` and opt-in `JSONBridges` in the same way when needed. A local checkout can use `.package(path: "../Swift-JSON")`. The requirement above accepts compatible 1.x -releases. See the [six-product consumer](Examples/Consumer) for a complete buildable +releases. See the [seven-library consumer](Examples/Consumer) for a complete buildable manifest. The website tracks `main`; release assets include compiled DocC catalogues for the exact released source. @@ -302,6 +314,45 @@ states, captures, matching work and cancellation are budgeted. Exact-spelling pr share immutable storage within a plan. See the [schema and regex contract](Documentation/Architecture/SchemaAndRegex.md) and [public consumer interface](Documentation/ConsumerInterface.md). +## Generate verified schema examples (1.6 development) + +In the isolated 1.6 checkout, add the optional `JSONSchemaExamples` product to your +target alongside JSON and JSONSchema. Existing released-version requirements above +do not promise this development API. The generator is useful for deterministic test +fixtures, documentation samples and schema-driven tooling; it is a conservative +search, with complete native validation required for every returned value. + +```swift +import JSONSchemaExamples + +let exampleSchema = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"integer","minimum":9007199254740993}"# +)) +let exampleGenerator = try JSONSchemaExampleGenerator(document: exampleSchema) +let schemaExample = try exampleGenerator.generate(seed: 42, profile: .minimal) +let exampleMinimum = try JSONNumber(parsing: "9007199254740993") +precondition(schemaExample.validation.isValid) +precondition(schemaExample.value.numberValue == exampleMinimum) +precondition(schemaExample.statistics[.evaluationWorkUnits] > 0) +``` + +Preparation owns a separate finite compilation/snapshot ledger. Each generation +shares one cumulative budget across discovery, staging, invalid candidates, native +validation and final trace hashing. Defaults/examples are hints; no defaults, +requiredness or format assertion are invented. A `$defs`-only document needs an +explicit root `$ref` to the desired definition. Registries remain offline and +immutable. `.demonstration` uses a versioned seeded synthetic corpus, without an +AI service or network access. + +The result binds source, ordered corpus, policies, algorithm and complete trace +identities. Search exhaustion or unsupported construction can occur for satisfiable +schemas. Operational errors and cancellation terminate the call with no candidate +or successful report. Logical retention snapshots certification and conservative +staging reservations; it is not heap size or RSS. Read the [getting-started guide](Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md), +[supported subset](Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md), +[policies and limits](Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md) +and [current API contract](Documentation/Architecture/Release1.6/API.md). + ## Apply transactional patches ```swift @@ -453,11 +504,11 @@ It includes unreleased APIs. For the exact released 1.5.0 interface, use its [tagged source documentation](https://github.com/ThorFuchs/Swift-JSON/tree/1.5.0/Sources) or its [compiled DocC asset](https://github.com/ThorFuchs/Swift-JSON/releases/tag/1.5.0). -The [documentation index](Documentation/README.md) links the behavior contracts. The six DocC catalogs +The [documentation index](Documentation/README.md) links the behavior contracts. The eight DocC catalogs provide public API references, focused guides, parameter descriptions and failure contracts, compiled from the native Swift public symbol graphs. -Build all seven candidate catalogs on a supported native host: +Build all eight candidate catalogs on a supported native host: ```sh python3 Scripts/ci.py --lane docs @@ -530,7 +581,7 @@ swift build -c release --product swift-json ### Run exact pull queries -The package has six libraries plus the executable, with no external root +The development package has eight libraries plus the executable, with no external root dependencies. Build the selected source version with Swift6.4+ (`swift build -c release --product swift-json`), or use `swift run swift-json help`. Native qualification requires macOS/SDK27+. @@ -576,4 +627,4 @@ Add the JSONNavigation product and import it explicitly. Existing one-node reads remain in JSON. Full source admission precedes traversal option validation. See the [compiled traversal guide](Sources/JSONNavigation/Documentation.docc/Traversal.md) and [real external SwiftPM consumer](Tools/NavigationQualification). This branch -prepares eight products; published 1.5.0 and its seven-product contract are unchanged. +prepares nine products; published 1.5.0 and its seven-product contract are unchanged. diff --git a/Scripts/README.md b/Scripts/README.md index 50d07a0..780fe1e 100644 --- a/Scripts/README.md +++ b/Scripts/README.md @@ -10,11 +10,13 @@ separate from the dependency-free library. Native lanes fail on unsupported host | `python3 Scripts/ci.py --lane native` | Manifests, builds, Swift Testing, consumers and suites | | `python3 Scripts/ci.py --lane safety` | Address/Thread Sanitizer and strict memory safety | | `python3 Scripts/ci.py --lane navigation` | N1 product exact discovery, Debug/Release, strict-memory and real external SwiftPM consumer/compiler/DocC proof; no release action | -| `python3 Scripts/ci.py --lane linux` | Actual Linux preflight, all eight candidate products, exact root/cap/corpus/consumer counts | +| `python3 Scripts/ci.py --lane linux` | Actual Linux preflight, all nine products, exact root/cap/corpus/consumer counts | | `python3 Scripts/ci.py --lane linux-safety` | Linux Address/Thread Sanitizer and strict memory safety | | `python3 Scripts/ci.py --lane wasm` | Matched Swift 6.4 WASI debug/release, exact root/corpus/cap/consumer counts, two-host public parity and structured resource failures | | `python3 Scripts/qualify_linux_reproduction.py --output PATH` | Actual source archive, byte-equal rebuild, clean extraction, build/tests and public consumer | -| `python3 Scripts/ci.py --lane docs` | Seven DocC catalogs, authored API/parameter coverage, executed README examples and verified static Pages site | +| `python3 Scripts/ci.py --lane docs` | Eight DocC catalogs, authored API/parameter coverage, executed README examples and verified static Pages site | +| `python3 Scripts/ci.py --lane schema-examples` | G1 focused native tests, exact optional-product graph and actual public consumer/access-control probes | +| `python3 Scripts/ci.py --lane schema-examples-safety` | G1 Release ASan, shared-generator TSan and strict memory-safety compilation; supplements full safety | | `python3 Scripts/build_documentation_site.py` | Transform already-qualified DocC archives and verify static routes/assets | | `python3 Scripts/qualification_watchdog.py --timeout 900 --output PATH -- COMMAND` | Process-group timeout and native receipt | | `python3 Scripts/archive_candidate.py --output PATH` | Public source archive with exact hashes and byte-equal rebuild | @@ -27,6 +29,10 @@ and original frozen archives stay in ignored `Artifacts/`. `Scripts/qualify_ergonomics.py` is part of native/Linux CI: eight real-module compiler probes and three synchronized, executed public guide fences. +`schema_example_documentation.py` synchronizes all ten S3 DocC Swift blocks with +their public consumer. `verify_schema_example_documentation.py` rejects count/import/ +drift and receipt mutations; the native/docs qualifiers execute the ten blocks in +two actual processes. Root README fences execute in the seven-library consumer. The maintenance repair restores `--lane coverage` / `--lane linux-coverage` as explicit profiling tools, `--lane linux-qa` and `--lane wasm-qa` as scoped execution lanes. Their receipts retain precise scopes; preparation does not establish a pass. diff --git a/Scripts/build_documentation.py b/Scripts/build_documentation.py index 9bdd2ff..c1adb35 100644 --- a/Scripts/build_documentation.py +++ b/Scripts/build_documentation.py @@ -12,6 +12,7 @@ import tarfile import documentation_catalog +import schema_example_documentation from preflight import collect ROOT = pathlib.Path(__file__).resolve().parents[1] @@ -81,6 +82,7 @@ def main(): receipt['coverage'] = coverage if coverage['errors']: raise ValueError('Incomplete authored public API documentation') + receipt['schema_examples_documentation'] = schema_example_documentation.check() example_source, example_count = documentation_catalog.readme_examples() example = ROOT / 'Examples/Consumer/Sources/Consumer/READMEExamples.swift' if example.read_text() != example_source: @@ -89,6 +91,10 @@ def main(): receipt['readme_examples'] = dict(run(command, output / 'readme-examples.log', 900), executable_fences=example_count) if receipt['readme_examples']['exit_code']: raise ValueError('Compiled README examples failed') + command = ['python3', '-B', 'Scripts/qualify_schema_examples.py', '--output', str(output.relative_to(ROOT) / 'schema-examples-consumer'), '--scratch-path', '.build'] + receipt['schema_examples_consumer'] = run(command, output / 'schema-examples-consumer.log', 900) + if receipt['schema_examples_consumer']['exit_code']: + raise ValueError('Public schema-examples consumer or compiler boundaries failed') for product in documentation_catalog.PRODUCTS: graphs = output / 'symbols' / product if graphs.exists(): diff --git a/Scripts/build_documentation_site.py b/Scripts/build_documentation_site.py index ccc2f03..b7ebb6a 100644 --- a/Scripts/build_documentation_site.py +++ b/Scripts/build_documentation_site.py @@ -22,6 +22,7 @@ SITE_URL = 'https://thorfuchs.github.io' + BASE_PATH + '/' GUIDES = {'JSON': ('Exact values and native coding', 'exactvaluesandcoding'), 'JSONSchema': ('Offline schema validation', 'offlinevalidation'), + 'JSONSchemaExamples': ('Verified exact candidates', 'verifiedcandidates'), 'JSONStreaming': ('Completion and backpressure', 'completionandbackpressure'), 'JSONPatch': ('Choosing and applying patches', 'applyingpatches'), 'JSONHTML': ('Exact interactive HTML', 'interactiverendering'), @@ -30,6 +31,7 @@ DESCRIPTIONS = { 'JSON': 'Exact values, strict UTF-8, native Codable, Pointer, presence and finite resource reports.', 'JSONSchema': 'Draft 2020-12, explicit offline registries, immutable plans, annotations and bounded regex.', + 'JSONSchemaExamples': 'Opt-in deterministic exact candidates and conservative typed proposals, with native offline certification and finite budgets.', 'JSONStreaming': 'Pull events and values, strict NDJSON, root-array completion and awaited output chunks.', 'JSONPatch': 'Transactional RFC 6902 JSON Patch and RFC 7396 Merge Patch with exact value semantics.', 'JSONHTML': 'Exact standalone HTML with offline disclosure/search and native-byte copy/download.', @@ -101,7 +103,7 @@ def landing(commit):
Public Swift documentation

Exact JSON. Clear contracts.

-

Keep decimal values exact, Unicode scalar keys distinct and resource use explicit. Explore seven Swift libraries that carry those semantics from input bytes to validated, transformed and encoded output.

+

Keep decimal values exact, Unicode scalar keys distinct and resource use explicit. Explore eight Swift libraries that carry those semantics from input bytes to validated, transformed and encoded output.

Swift 6.4+ · Swift language mode 6 · macOS/SDK 27+ · No package dependencies

{''.join(cards)}

Start with the contract

@@ -189,6 +191,13 @@ def main(): for route in [product.lower(), 'documentation/' + GUIDES[product][1]]: if not (site / product / 'documentation' / route / 'index.html').is_file(): raise ValueError('Missing product root or guide: ' + route) + if product == 'JSONSchemaExamples': + guides = ['verifiedcandidates', 'typedconstruction', 'advancedconstruction', + 'deterministicconstruction', 'policiesandlimits'] + for guide in guides: + if not (site / product / 'documentation/documentation' / guide / 'index.html').is_file(): + raise ValueError('Missing schema-examples guide: ' + guide) + receipt['schema_example_guides_verified'] = guides # DocC transforms its emitted routes but retains the original archive # root template. Make the product entry point an explicit static redirect. route = BASE_PATH + '/' + product + '/documentation/' + product.lower() + '/' diff --git a/Scripts/check_schema_example_determinism.py b/Scripts/check_schema_example_determinism.py new file mode 100644 index 0000000..7d3eb95 --- /dev/null +++ b/Scripts/check_schema_example_determinism.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Independent stdlib byte-transport/PRNG oracle for the public native G2 consumer.""" +import argparse +import hashlib +import json +import pathlib +import struct + +ROOT = pathlib.Path(__file__).resolve().parents[1] +MASK = (1 << 64) - 1 + + +def u64(value): + return struct.pack('>Q', value) + + +def field(value): + return u64(len(value)) + value + + +def digest(value): + return hashlib.sha256(value).hexdigest() + + +def draws(seed): + while True: + seed = (seed + 0x9E3779B97F4A7C15) & MASK + z = seed + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & MASK + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & MASK + yield z ^ (z >> 31) + + +def vectors(): + snapshot = digest(b'SwiftJSON-schema-snapshot-v1\n' + u64(1) + b'\0' + field(b'') + field(b'true')) + corpus = digest(b'SwiftJSON-example-corpus-v1\n' + field(b'[null,false,0,"example",[],{}]')) + profiles = json.loads((ROOT / 'Documentation/Architecture/ResourceProfiles.json').read_text()) + metrics = sum([profiles[k] for k in ['core', 'schema', 'streaming', 'patch', 'enhancement_limits']], []) + metrics = [dict(m, metric=m.get('metric', m.get('id'))) for m in metrics] + assert len(metrics) == 49 and len({m['metric'] for m in metrics}) == 49 + record = u64(49) + b''.join(field(m['metric'].encode()) + u64(m['balanced']) for m in metrics) + policy = digest(b'SwiftJSON-example-policy-v1\n' + u64(16) + record * 3 + bytes([0, 1, 0, 0]) * 2) + result = {'snapshot': snapshot, 'corpus': corpus, 'policy': policy} + for name, profile in [('minimal', 0), ('demonstration', 1)]: + trace = b'SwiftJSON-example-trace-v2\n' + b''.join(field(s.encode()) for s in ['s3-candidates-v4', snapshot, corpus, policy]) + u64(42) + bytes([profile]) + events = [(1, 0, 0)] + index = 0 + if profile: + raw = next(draws(42)) # Bound six has threshold four; this draw is accepted. + assert raw >= ((1 << 64) % 6) + events += [(3, raw, 6)] + index = raw % 6 + events += [(2, 4, index), (4, 1, 1), (5, 1, profile)] + trace += b''.join(bytes([code]) + u64(a) + u64(b) for code, a, b in events) + values = [b'null', b'false', b'0', b'"example"', b'[]', b'{}'] + result[name] = {'trace': digest(trace), 'events': len(events), 'draws': profile, + 'value_hex': values[index].hex(), 'trace_bytes': len(trace)} + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--consumer-log') + args = parser.parse_args() + expected = vectors() + if args.consumer_log: + lines = pathlib.Path(args.consumer_log).read_text().splitlines() + observed = [json.loads(line.removeprefix('G2_VECTORS ')) for line in lines if line.startswith('G2_VECTORS ')] + assert len(observed) == 1 and observed[0] == expected, (observed, expected) + print('SCHEMA_EXAMPLES_DETERMINISM passed profiles=2 metrics=49') + else: + print(json.dumps(expected, sort_keys=True, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/Scripts/ci.py b/Scripts/ci.py index d99640a..fbdc1b4 100644 --- a/Scripts/ci.py +++ b/Scripts/ci.py @@ -11,6 +11,7 @@ SHARED_TESTS = 'independentCodecFacadesAndExactValuesCanBeSharedAcrossTasks|concurrentSchemaEvaluationsHaveExclusiveDynamicRegexAndAnnotationState|sharedRegexProgramHasExclusiveConcurrentSearchState|immutableIndexedObjectCanBeSharedAcrossTasks|qaSharedPayloadAndRootHaveIndependentConcurrentReadScopes|sha256InputCapacityAndConcurrentInvocationsRemainIndependent|sharedFormattedHTMLRendererHasIndependentConcurrentInvocations|sharedAnnotatedDocumentRendererHasIndependentConcurrentInvocations|sharedExportPreparationsHaveIndependentConcurrentState|applePDFSimultaneousJobsHaveIndependentStateAndCancellation|sharedFilterPlanHasIndependentConcurrentInvocations|structuredSharedRendererPlansHaveInvocationOwnedState|sharedProviderBridgeHasIndependentConcurrentInvocations|e2SharedBridgeInputsHaveInvocationOwnedState|e2SharedCompiledPlanFacadeHasIndependentInvocationState' SHARED_TESTS += '|compositionIndependentScopesCanBeSharedAcrossTasks' SHARED_TESTS += '|qaStreaming|qaE2E(SharedPlan|RootImport|NDJSONImport|AwaitedSink|ExactWorkflow)' +SHARED_TESTS += '|g1SharedPreparedGeneratorHasIndependentConcurrentInvocations|g3SharedTypedGeneratorHasIndependentConcurrentInvocations|g4SharedAdvancedGeneratorHasIndependentConcurrentInvocations|g5SharedPreparationSeparatesConcurrentFailureAndSuccessHealth' SHARED_TESTS += '|navigationSharedImmutableRootIndependentInvocations' R0_TESTS = 'readerProbe|indexProbes|duplicateRecognitionPrecedesValueAndClosingSyntax' @@ -20,6 +21,33 @@ def snapshot(): def commands(lane, output): # The qualification executable resolves output paths from its repository root. output = output.relative_to(ROOT) + if lane == 'schema-examples-qualification': + return commands('schema-examples-advanced', ROOT / output) + [['python3', '-B', 'Scripts/qualify_schema_example_matrix.py', '--output', str(output / 'matrix')]] + if lane == 'schema-examples-advanced': + return commands('schema-examples-construction', ROOT / output) + [['python3', '-B', 'Scripts/qualify_schema_example_advanced.py', '--output', str(output / 'advanced')]] + if lane in ('schema-examples-determinism', 'schema-examples-construction'): + # G2 changes only the opt-in generator. Reuse unchanged-core schema receipts; + # keep the broader schema-examples lane and every full release gate intact. + baseline = commands('schema-examples', ROOT / output) + return baseline[:4] + [ + ['swift', 'test', '-c', 'release', '--jobs', '2', '--test-product', 'JSONSchemaExamplesTests'], + ['python3', '-B', 'Scripts/qualify_schema_example_ownership.py', '--output', str(output / 'ownership')], + ] + baseline[5:] + ([['python3', '-B', 'Scripts/qualify_schema_example_generation.py', '--output', str(output / 'generation')]] if lane == 'schema-examples-construction' else []) + if lane == 'schema-examples-safety': + return [ + ['swift', 'test', '-c', 'release', '--jobs', '2', '--test-product', 'JSONSchemaExamplesTests', '--sanitize', 'address', '--scratch-path', '.build/schema-examples-address'], + ['swift', 'test', '-c', 'release', '--jobs', '2', '--test-product', 'JSONSchemaExamplesTests', '--sanitize', 'thread', '--scratch-path', '.build/schema-examples-thread', '--filter', 'g1SharedPreparedGeneratorHasIndependentConcurrentInvocations|g3SharedTypedGeneratorHasIndependentConcurrentInvocations|g4SharedAdvancedGeneratorHasIndependentConcurrentInvocations|g5SharedPreparationSeparatesConcurrentFailureAndSuccessHealth'], + ['swift', 'build', '--target', 'JSONSchemaExamples', '--jobs', '2', '--scratch-path', '.build/schema-examples-strict', '-Xswiftc', '-strict-memory-safety', '-Xswiftc', '-warnings-as-errors'], + ] + if lane == 'schema-examples': + return [ + ['python3', '-B', 'Scripts/public_check.py'], + ['swift', 'package', 'dump-package'], + ['swift', 'package', 'show-dependencies', '--format', 'json'], + ['swift', 'test', '--jobs', '2', '--test-product', 'JSONSchemaExamplesTests', '--filter', 'JSONSchemaExamplesTests'], + ['swift', 'test', '--jobs', '2', '--test-product', 'JSONSchemaTests'], + ['python3', '-B', 'Scripts/qualify_schema_examples.py', '--output', str(output / 'consumer'), '--scratch-path', '.build'], + ] if lane == 'composition': result = [ ['swift', 'test', '--filter', 'composition|composed|caughtOperationalFailureCannotComplete'], @@ -118,6 +146,11 @@ def commands(lane, output): ['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')], ['swift', 'run', '-c', 'release', '--package-path', 'Examples/Consumer', 'Consumer'], + ['python3', '-B', 'Scripts/qualify_schema_examples.py', '--output', str(output / 'schema-examples'), '--scratch-path', '.build'], + ['python3', '-B', 'Scripts/qualify_schema_example_ownership.py', '--output', str(output / 'schema-example-ownership')], + ['python3', '-B', 'Scripts/qualify_schema_example_generation.py', '--output', str(output / 'schema-example-generation')], + ['python3', '-B', 'Scripts/qualify_schema_example_advanced.py', '--output', str(output / 'schema-example-advanced')], + ['python3', '-B', 'Scripts/qualify_schema_example_matrix.py', '--output', str(output / 'schema-example-matrix')], ['python3', '-B', 'Scripts/qualify_distribution.py', '--output', str(output / 'distribution')], ['python3', '-B', 'Scripts/qualify_ergonomics.py'], ['python3', '-B', 'Scripts/qualify_navigation.py', '--output', str(output / 'navigation')], @@ -133,6 +166,7 @@ def commands(lane, output): ['swift', 'test', '-c', 'release', '--sanitize', 'address', '--scratch-path', '.build/ci-address'], ['swift', 'test', '-c', 'release', '--sanitize', 'thread', '--scratch-path', '.build/ci-thread', '--filter', SHARED_TESTS], ['swift', 'build', '--scratch-path', '.build/ci-strict', '-Xswiftc', '-strict-memory-safety', '-Xswiftc', '-warnings-as-errors'], + ['python3', '-B', 'Scripts/qualify_schema_example_lifetime.py', '--output', str(output / 'corpus-lifetime')], ] if lane == 'r0': return [ @@ -168,7 +202,12 @@ def check_result(command, code, log, events=None): passed = code == 0 if events is not None: counts = events['bundle_counts'] if command[:2] == ['swift', 'test']: - expected = [DISCOVERY['composition_external_functions']] if 'Tools/CompositionQualification' in command else [12] if 'composition|composed|caughtOperationalFailureCannotComplete' in command else [88] if 'cli|sharedFilterPlan' in command else sorted(DISCOVERY['export_functions'].values()) if 'export|Export|apple' in command else [14] if 'Tools/WasmAudit' in command else sorted(DISCOVERY['qa_functions'].values()) 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 sorted(SHARED_COUNTS.values()) if 'thread' in command else sorted(ROOT_COUNTS.values()) + expected = [DISCOVERY['composition_external_functions']] if 'Tools/CompositionQualification' in command else [12] if 'composition|composed|caughtOperationalFailureCannotComplete' in command else [79] if 'JSONSchemaTests' in command else [4 if 'thread' in command else 98] if 'JSONSchemaExamplesTests' in command else [88] if 'cli|sharedFilterPlan' in command else sorted(DISCOVERY['export_functions'].values()) if 'export|Export|apple' in command else [14] if 'Tools/WasmAudit' in command else sorted(DISCOVERY['qa_functions'].values()) 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 sorted(SHARED_COUNTS.values()) if 'thread' in command else sorted(ROOT_COUNTS.values()) + if 'JSONSchemaExamplesTests' in command and events is not None: + passed = passed and events['module_function_counts'] == {'JSONSchemaExamplesTests': expected[0]} and events['parameter_case_invocations'] == 0 + if 'JSONSchemaTests' in command and events is not None: + parameters = {name: ROOT_PARAMETER_CASES[name] for name in ['successfulAlternativesCannotSwallowRegexExhaustion(keyword:)', 'URIIdentityUsesSyntaxNormalizationWithoutUnicodeFolding(input:expected:)']} + passed = passed and events['module_function_counts'] == {'JSONSchemaTests': 79} and events['parameterized_functions'] == parameters and events['parameter_case_invocations'] == 12 if 'navigation' in command: expected = [DISCOVERY['root_functions']['JSONNavigationTests']] if events is not None: @@ -178,9 +217,9 @@ def check_result(command, code, log, events=None): passed = passed and events['module_function_counts'] == {'CompositionScaleTests': DISCOVERY['composition_external_functions']} and events['parameter_case_invocations'] == 0 if 'qa' in command and events is not None: passed = passed and events['module_function_counts'] == DISCOVERY['qa_functions'] and check_restored(events) - if 'thread' in command and events is not None: + if 'thread' in command and 'JSONSchemaExamplesTests' not in command and events is not None: passed = passed and events['module_function_counts'] == SHARED_COUNTS - if '--filter' not in command and not any(x.startswith('Tools/') for x in command): + if '--filter' not in command and '--test-product' not in command and not any(x.startswith('Tools/') for x in command): if events is not None: passed = passed and events['module_function_counts'] == ROOT_COUNTS and check_restored(events) 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)) == PARAMETER_INVOCATIONS) @@ -210,10 +249,24 @@ def check_result(command, code, log, events=None): passed = data['observed'] == data['passed'] == expected and data['failed'] == data['policy'] == data['blocked'] == 0 if command == ['swift', 'package', 'dump-package'] and passed: d = json.loads(log) - passed = sorted(p['name'] for p in d['products']) == ['JSON', 'JSONBridges', 'JSONHTML', 'JSONNavigation', 'JSONPatch', 'JSONSchema', 'JSONStreaming', 'swift-json'] and not d['dependencies'] and d['swiftLanguageVersions'] == ['6'] and d['toolsVersion']['_version'] == '6.4.0' and d['platforms'] == [{'options': [], 'platformName': 'macos', 'version': '27.0'}, {'options': [], 'platformName': 'ios', 'version': '27.0'}] - passed = passed and {p['name']: p['targets'] for p in d['products']} == dict({name: [name] for name in ['JSON', 'JSONSchema', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONBridges', 'JSONNavigation']}, **{'swift-json': ['JSONCLI']}) + passed = sorted(p['name'] for p in d['products']) == ['JSON', 'JSONBridges', 'JSONHTML', 'JSONNavigation', 'JSONPatch', 'JSONSchema', 'JSONSchemaExamples', 'JSONStreaming', 'swift-json'] and not d['dependencies'] and d['swiftLanguageVersions'] == ['6'] and d['toolsVersion']['_version'] == '6.4.0' and d['platforms'] == [{'options': [], 'platformName': 'macos', 'version': '27.0'}, {'options': [], 'platformName': 'ios', 'version': '27.0'}] + passed = passed and {p['name']: p['targets'] for p in d['products']} == dict({name: [name] for name in ['JSON', 'JSONSchema', 'JSONSchemaExamples', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONBridges', 'JSONNavigation']}, **{'swift-json': ['JSONCLI']}) targets = {t['name']: t for t in d['targets']} - passed = passed and set(targets) == {'JSON', 'JSONNavigation', 'JSONNavigationTests', 'JSONIntegrationTests', 'JSONBridges', 'JSONBridgesTests', 'JSONSchema', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONRegexRuntime', 'JSONTests', 'JSONSchemaTests', 'JSONStreamingTests', 'JSONPatchTests', 'JSONHTMLTests', 'JSONRegexRuntimeTests', 'JSONFilterRuntime', 'JSONCLIEngine', 'JSONCLI', 'JSONCLITests'} and targets['JSONHTML']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONBridges']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONNavigation']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONNavigationTests']['dependencies'] == [{'byName': ['JSONNavigation', None]}, {'byName': ['JSON', None]}] and targets['JSONFilterRuntime']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONCLI']['type'] == 'executable' + passed = passed and set(targets) == {'JSON', 'JSONNavigation', 'JSONNavigationTests', 'JSONIntegrationTests', 'JSONBridges', 'JSONBridgesTests', 'JSONSchema', 'JSONSchemaExamples', 'JSONSchemaExamplesTests', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONRegexRuntime', 'JSONTests', 'JSONSchemaTests', 'JSONStreamingTests', 'JSONPatchTests', 'JSONHTMLTests', 'JSONRegexRuntimeTests', 'JSONFilterRuntime', 'JSONCLIEngine', 'JSONCLI', 'JSONCLITests'} and targets['JSONHTML']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONBridges']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONFilterRuntime']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONCLI']['type'] == 'executable' + passed = passed and targets['JSONNavigation']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONNavigationTests']['dependencies'] == [{'byName': ['JSONNavigation', None]}, {'byName': ['JSON', None]}] + passed = passed and targets['JSONSchemaExamples']['dependencies'] == [{'byName': ['JSON', None]}, {'byName': ['JSONSchema', None]}] and targets['JSON']['dependencies'] == [] and targets['JSONSchema']['dependencies'] == [{'byName': ['JSON', None]}, {'byName': ['JSONRegexRuntime', None]}] + if command[:3] == ['python3', '-B', 'Scripts/qualify_schema_example_matrix.py']: + passed = passed and 'SCHEMA_EXAMPLES_MATRIX passed schemas=64 certified=512 invalid=64 processes=2' in log.splitlines() + if command[:3] == ['python3', '-B', 'Scripts/qualify_schema_example_lifetime.py']: + passed = passed and 'SCHEMA_EXAMPLES_CORPUS passed mode=owned scopes=128 values=6 exact=1' in log.splitlines() + if command[:3] == ['python3', '-B', 'Scripts/qualify_schema_example_advanced.py']: + passed = passed and 'SCHEMA_EXAMPLES_ADVANCED passed required=10 incomplete=8 profiles=2 seeds=4 per_process=144 processes=2 witnesses=155' in log.splitlines() + if command[:3] == ['python3', '-B', 'Scripts/qualify_schema_example_generation.py']: + passed = passed and 'SCHEMA_EXAMPLES_GENERATION passed cases=24 profiles=2 seeds=4 per_process=192 processes=2 proofs=2' in log.splitlines() + if command[:3] == ['python3', '-B', 'Scripts/qualify_schema_example_ownership.py']: + passed = passed and 'SCHEMA_EXAMPLES_OWNERSHIP passed positive=1 negatives=8' in log.splitlines() + if command[:3] == ['python3', '-B', 'Scripts/qualify_schema_examples.py']: + passed = passed and 'SCHEMA_EXAMPLES_COMPILER passed public_cases=15 negatives=2' in log.splitlines() and 'SCHEMA_EXAMPLES_DOCUMENTATION_COMPILER passed snippets=10 processes=2' in log.splitlines() if command == ['swift', 'package', 'show-dependencies', '--format', 'json'] and passed: passed = json.loads(log)['dependencies'] == [] if command[:2] == ['swift', 'run'] and 'CompositionConsumer' in command: @@ -240,7 +293,7 @@ def check_result(command, code, log, events=None): def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--lane', choices=['composition', 'native', 'safety', 'docs', 'r0', 'qa', 'linux', 'linux-safety', 'wasm', 'browser', 'export', 'cli', 'coverage', 'linux-coverage', 'linux-qa', 'wasm-qa', 'archive', 'navigation'], required=True) + parser.add_argument('--lane', choices=['composition', 'navigation', 'native', 'safety', 'docs', 'r0', 'qa', 'linux', 'linux-safety', 'wasm', 'browser', 'export', 'cli', 'schema-examples', 'schema-examples-safety', 'schema-examples-determinism', 'schema-examples-construction', 'schema-examples-advanced', 'schema-examples-qualification', 'coverage', 'linux-coverage', 'linux-qa', 'wasm-qa', 'archive'], required=True) parser.add_argument('--output') args = parser.parse_args() output = ROOT / (args.output or '.build/ci/' + args.lane) diff --git a/Scripts/coverage.py b/Scripts/coverage.py index 13190a1..fefa0a6 100644 --- a/Scripts/coverage.py +++ b/Scripts/coverage.py @@ -46,7 +46,7 @@ def shared_objects(bin_directory): def without_schema_objects(objects): # Integration imports schema too, including the private regex implementation. - omitted = {'JSONSchemaTests/runner', 'JSONSchemaTests', 'JSONSchema', + omitted = {'JSONSchemaExamplesTests/runner', 'JSONSchemaExamplesTests', 'JSONSchemaExamples', 'JSONSchemaTests/runner', 'JSONSchemaTests', 'JSONSchema', 'JSONIntegrationTests/runner', 'JSONIntegrationTests', 'JSONCLITests/runner', 'JSONCLITests', 'JSONCLIEngine', 'swift-json/runner'} return {name:path for name,path in objects.items() if name not in omitted} diff --git a/Scripts/documentation_catalog.py b/Scripts/documentation_catalog.py index 9458193..abf396b 100644 --- a/Scripts/documentation_catalog.py +++ b/Scripts/documentation_catalog.py @@ -7,7 +7,7 @@ import urllib.parse ROOT = pathlib.Path(__file__).resolve().parents[1] -PRODUCTS = ('JSON', 'JSONSchema', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONBridges', 'JSONNavigation') +PRODUCTS = ('JSON', 'JSONSchema', 'JSONSchemaExamples', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONBridges', 'JSONNavigation') def public_symbols(graph_dir): diff --git a/Scripts/qualify_schema_example_advanced.py b/Scripts/qualify_schema_example_advanced.py new file mode 100644 index 0000000..983c12e --- /dev/null +++ b/Scripts/qualify_schema_example_advanced.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Materialize ten G4 successes and eight honest incomplete-search cases through public APIs.""" +import argparse, hashlib, json, pathlib, subprocess, sys +from decimal import Decimal + +ROOT = pathlib.Path(__file__).resolve().parents[1] +ORIGINAL = '6f2bfd4fdb7c7e206b6570c2156ca8e248831840733b0258aa8cd5938b35649f' +AMENDED = '5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3' + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', required=True) + parser.add_argument('--scratch-path', default='.build') + args = parser.parse_args() + output = ROOT / args.output + output.mkdir(parents=True, exist_ok=True) + rows = [] + receipt = {'schema_version': 1, 'status': 'failed', 'commands': rows, + 'scope': 'G4 public native construction and honest incompleteness; all 155 native witness checks, not new external-oracle or G7 platform qualification'} + + def run(name, command): + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=1200) + text = result.stdout + result.stderr + log = output / (name + '.log') + log.write_text(text.replace(str(ROOT), '.')) + rows.append({'command': command, 'exit_code': result.returncode, 'log': log.relative_to(ROOT).as_posix(), + 'log_sha256': hashlib.sha256(log.read_bytes()).hexdigest()}) + if result.returncode: raise ValueError('Failed actual public consumer: ' + name) + return text + + try: + fixtures = ROOT / 'Tests/Fixtures/SchemaExamples' + fixture = fixtures / 'S3-integral-correction-v1.json' + manifest_path = fixtures / 'Manifest.json' + manifest = json.loads(manifest_path.read_text()) + assert hashlib.sha256(fixture.read_bytes()).hexdigest() == manifest['amended_sha256'] == AMENDED + assert hashlib.sha256((fixtures / 'S3-original-v1.json').read_bytes()).hexdigest() == manifest['original_sha256'] == ORIGINAL + required_ids = manifest['g4_must_generate_ids'] + incomplete_ids = manifest['g4_may_fail_ids'] + assert len(required_ids) == len(set(required_ids)) == 10 + assert len(incomplete_ids) == len(set(incomplete_ids)) == 8 + assert not set(required_ids) & set(incomplete_ids) + location = run('bin-path', ['swift', 'build', '--package-path', 'Examples/SchemaExamplesConsumer', '--show-bin-path', '--scratch-path', args.scratch_path]) + bins = [pathlib.Path(line) for line in location.splitlines() if line.startswith('/')] + assert len(bins) == 1 + binary = bins[0] / 'SchemaExamplesConsumer' + receipts = [] + for index in range(2): + text = run('generation-process-' + str(index), [str(binary), '--g4-fixtures', str(fixture), '--g4-manifest', str(manifest_path)]) + assert 'SCHEMA_EXAMPLES_G4 passed required=10 incomplete=8 profiles=2 seeds=4 invocations=144 witnesses=155' in text.splitlines() + report_lines = [line.removeprefix('G4_RECEIPT ') for line in text.splitlines() if line.startswith('G4_RECEIPT ')] + assert len(report_lines) == 1 + report = json.loads(report_lines[0], parse_float=Decimal) + assert report['requiredIDs'] == required_ids and report['incompleteIDs'] == incomplete_ids + assert report['schemas'] == 44 and report['witnesses'] == 155 and report['mustValid'] == 80 + assert report['algorithm'] == 's3-candidates-v4' and len(report['results']) == 144 + expected = {(name, profile, seed) for name in required_ids + incomplete_ids for profile in ['minimal','demonstration'] for seed in [0,1,42,2**64-1]} + observed = {(r['id'],r['profile'],r['seed']) for r in report['results']} + assert observed == expected + must_valid = may_valid = explicit = 0 + for r in report['results']: + required = r['id'] in required_ids + assert r['required'] is required + if r['outcome'] == 'validated': + assert r['nativeCertified'] is r['independentNativeValid'] is r['roundtrip'] is True + assert 0 < r['evaluationWork'] <= r['work'] and r['remainingWork'] >= 0 + assert 1 <= r['attempts'] <= 16 and len(r['trace']) == len(r['snapshot']) == len(r['corpus']) == len(r['policy']) == 64 + assert 'value' in r + if required: must_valid += 1 + else: may_valid += 1 + else: + assert not required and r['outcome'] in ['unsupported','searchExhausted'] + assert not any(k in r for k in ['value','work','evaluationWork','remainingWork','nativeCertified']) + if r['outcome'] == 'unsupported': assert r['reason'] in ['candidateConstruction','dynamicReference','cyclicReference'] + else: assert 1 <= r['attempts'] <= 16 + explicit += 1 + assert must_valid == 80 and may_valid + explicit == 64 + assert may_valid == report['mayValid'] and explicit == report['explicit'] + (output / ('generation-process-' + str(index) + '.json')).write_text(report_lines[0] + '\n') + receipts.append(report) + assert receipts[0] == receipts[1], 'Cross-process generation/trace/accounting drift' + receipt.update(status='passed', required_cases=10, incomplete_cases=8, profiles=2, seeds=4, + invocations_per_process=144, required_valid_per_process=80, may_valid_per_process=may_valid, + explicit_incomplete_per_process=explicit, processes=2, fixture_schemas=44, native_witnesses_per_process=155, + input_sha256=AMENDED, original_sha256=ORIGINAL, + binary_sha256=hashlib.sha256(binary.read_bytes()).hexdigest(), + required_case_ids=required_ids, incomplete_case_ids=incomplete_ids, + classification='A valid witness is success; unsupported/exhausted is incomplete search; native operational errors fail acceptance') + except (AssertionError, ValueError, OSError, subprocess.SubprocessError) as error: + receipt['error'] = str(error) or type(error).__name__ + (output / 'receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') + print('SCHEMA_EXAMPLES_ADVANCED ' + receipt['status'] + ' required=10 incomplete=8 profiles=2 seeds=4 per_process=144 processes=2 witnesses=155') + return 0 if receipt['status'] == 'passed' else 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Scripts/qualify_schema_example_generation.py b/Scripts/qualify_schema_example_generation.py new file mode 100644 index 0000000..bac1e39 --- /dev/null +++ b/Scripts/qualify_schema_example_generation.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Materialize the 24-case G3 acceptance matrix through actual public APIs.""" +import argparse, hashlib, json, pathlib, subprocess, sys +from decimal import Decimal + +ROOT = pathlib.Path(__file__).resolve().parents[1] +ORIGINAL = '6f2bfd4fdb7c7e206b6570c2156ca8e248831840733b0258aa8cd5938b35649f' +AMENDED = '5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3' + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', required=True) + parser.add_argument('--scratch-path', default='.build') + args = parser.parse_args() + output = ROOT / args.output + output.mkdir(parents=True, exist_ok=True) + rows = [] + receipt = {'schema_version': 1, 'status': 'failed', 'commands': rows, + 'scope': 'Public native generation/validation/roundtrip; not an independent external validator or G4/G7 qualification'} + + def run(name, command): + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=1200) + text = result.stdout + result.stderr + log = output / (name + '.log') + log.write_text(text.replace(str(ROOT), '.')) + rows.append({'command': command, 'exit_code': result.returncode, 'log': log.relative_to(ROOT).as_posix(), + 'log_sha256': hashlib.sha256(log.read_bytes()).hexdigest()}) + if result.returncode: raise ValueError('Failed actual public consumer: ' + name) + return text + + try: + fixtures = ROOT / 'Tests/Fixtures/SchemaExamples' + fixture = fixtures / 'S3-integral-correction-v1.json' + manifest_path = fixtures / 'Manifest.json' + manifest = json.loads(manifest_path.read_text()) + assert hashlib.sha256(fixture.read_bytes()).hexdigest() == manifest['amended_sha256'] == AMENDED + assert hashlib.sha256((fixtures / 'S3-original-v1.json').read_bytes()).hexdigest() == manifest['original_sha256'] == ORIGINAL + expected_ids = manifest['g3_must_generate_ids'] + assert len(expected_ids) == len(set(expected_ids)) == 24 + location = run('bin-path', ['swift', 'build', '--package-path', 'Examples/SchemaExamplesConsumer', '--show-bin-path', '--scratch-path', args.scratch_path]) + bins = [pathlib.Path(line) for line in location.splitlines() if line.startswith('/')] + assert len(bins) == 1 + binary = bins[0] / 'SchemaExamplesConsumer' + receipts = [] + for index in range(2): + text = run('generation-process-' + str(index), [str(binary), '--g3-fixtures', str(fixture), '--g3-manifest', str(manifest_path)]) + assert 'SCHEMA_EXAMPLES_G3 passed cases=24 profiles=2 seeds=4 validated=192 witnesses=90 proofs=2' in text.splitlines() + report_lines = [line.removeprefix('G3_RECEIPT ') for line in text.splitlines() if line.startswith('G3_RECEIPT ')] + assert len(report_lines) == 1 + report = json.loads(report_lines[0], parse_float=Decimal) + assert report['ids'] == expected_ids and report['witnesses'] == 90 + assert report['proofs'] == manifest['g3_sound_proof_ids'] + assert report['algorithm'] == 's3-candidates-v4' and len(report['results']) == 192 + expected = {(name, profile, seed) for name in expected_ids for profile in ['minimal','demonstration'] for seed in [0,1,42,2**64-1]} + observed = {(r['id'],r['profile'],r['seed']) for r in report['results']} + assert observed == expected + for r in report['results']: + assert r['nativeCertified'] is r['independentNativeValid'] is r['roundtrip'] is True + assert 0 < r['evaluationWork'] <= r['work'] and r['remainingWork'] >= 0 + assert r['attempts'] >= 1 and len(r['trace']) == len(r['snapshot']) == len(r['corpus']) == len(r['policy']) == 64 + (output / ('generation-process-' + str(index) + '.json')).write_text(report_lines[0] + '\n') + receipts.append(report) + assert receipts[0] == receipts[1], 'Cross-process generation/trace/accounting drift' + receipt.update(status='passed', cases=24, profiles=2, seeds=4, generated_per_process=192, + processes=2, witnesses_per_process=90, sound_proofs_per_process=2, + input_sha256=AMENDED, original_sha256=ORIGINAL, + binary_sha256=hashlib.sha256(binary.read_bytes()).hexdigest(), + expected_case_ids=expected_ids, source_policies_excluded_from_successes=['18 G4 cases']) + except (AssertionError, ValueError, OSError, subprocess.SubprocessError) as error: + receipt['error'] = str(error) or type(error).__name__ + (output / 'receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') + print('SCHEMA_EXAMPLES_GENERATION ' + receipt['status'] + ' cases=24 profiles=2 seeds=4 per_process=192 processes=2 proofs=2') + return 0 if receipt['status'] == 'passed' else 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Scripts/qualify_schema_example_lifetime.py b/Scripts/qualify_schema_example_lifetime.py new file mode 100644 index 0000000..8a16eda --- /dev/null +++ b/Scripts/qualify_schema_example_lifetime.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""ASan public-consumer process lifetime: no suppressions or ignored leak exits.""" +import argparse, hashlib, json, os, pathlib, subprocess, sys +ROOT = pathlib.Path(__file__).resolve().parents[1] +SOURCE = r'''import JSON +import JSONSchemaExamples + +@inline(never) func checkCorpus() throws { + let corpus = JSONSchemaExampleCorpus.synthetic + let text = try JSONWriter().writeString(.array(corpus.values)) + precondition(text == #"[null,false,0,"example",[],{}]"#) + var changed = corpus.values + changed.append(.string("local")) + precondition(changed.count == 7 && corpus.values.count == 6) + precondition(JSONSchemaExampleOptions().corpus.values.count == 6) +} + +@main struct CorpusLifetime { + static func main() throws { + for _ in 0..<128 { try checkCorpus() } + print("SCHEMA_EXAMPLES_CORPUS passed mode=owned scopes=128 values=6 exact=1") + } +} +''' +MARKER = 'SCHEMA_EXAMPLES_CORPUS passed mode=owned scopes=128 values=6 exact=1' + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', required=True) + args = parser.parse_args() + out = ROOT / args.output; out.mkdir(parents=True, exist_ok=True) + package = out / 'consumer'; sources = package / 'Sources/CorpusLifetime'; sources.mkdir(parents=True, exist_ok=True) + (package / 'Package.swift').write_text('''// swift-tools-version: 6.4 +import PackageDescription +let package = Package(name: "CorpusLifetime", platforms: [.macOS("27.0"), .iOS("27.0")], + dependencies: [.package(name: "Swift-JSON", path: ''' + json.dumps(str(ROOT)) + ''')], + targets: [.executableTarget(name: "CorpusLifetime", dependencies: [ + .product(name: "JSON", package: "Swift-JSON"), + .product(name: "JSONSchemaExamples", package: "Swift-JSON")])], swiftLanguageModes: [.v6]) +''') + (sources / 'CorpusLifetime.swift').write_text(SOURCE) + report = dict(status='failed', commands=[], scopes=128, values=6, sanitizer_suppressions=False, + scope='Real public products, repeated exact synthetic corpus/options reads and caller copy mutation; process exit must be ASan/LSan clean.') + def run(name, command, timeout, environment=None): + log = out / (name + '.log') + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=timeout, env=environment) + log.write_text(result.stdout + result.stderr) + report['commands'].append(dict(command=command, exit_code=result.returncode, log=str(log.relative_to(ROOT)), log_sha256=hashlib.sha256(log.read_bytes()).hexdigest())) + if result.returncode: raise ValueError(name + ' exited ' + str(result.returncode)) + return log.read_text() + try: + build = ['swift', 'build', '-c', 'release', '--sanitize', 'address', '--jobs', '2', '--package-path', str(package), '--scratch-path', '.build/schema-example-lifetime'] + run('build', build, 1200) + path = run('bin-path', build + ['--show-bin-path'], 120) + binary = pathlib.Path(path.strip()) / 'CorpusLifetime' + environment = None + if sys.platform.startswith('linux'): + environment = dict(os.environ, ASAN_OPTIONS='detect_leaks=1', LSAN_OPTIONS='') + report['process_environment_overrides'] = {'ASAN_OPTIONS': 'detect_leaks=1', 'LSAN_OPTIONS': ''} + text = run('process', [str(binary)], 120, environment) + if text.splitlines() != [MARKER]: raise ValueError('Missing or additional process outcome') + report.update(status='passed', binary_sha256=hashlib.sha256(binary.read_bytes()).hexdigest()) + except (OSError, ValueError, subprocess.SubprocessError) as error: + report['error'] = str(error) + (out / 'receipt.json').write_text(json.dumps(report, indent=2) + '\n') + print(MARKER if report['status'] == 'passed' else 'SCHEMA_EXAMPLES_CORPUS failed') + return 0 if report['status'] == 'passed' else 1 + +if __name__ == '__main__': raise SystemExit(main()) diff --git a/Scripts/qualify_schema_example_matrix.py b/Scripts/qualify_schema_example_matrix.py new file mode 100644 index 0000000..0066d2a --- /dev/null +++ b/Scripts/qualify_schema_example_matrix.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Public bounded grammar replay, exact identities and two-process reproducibility.""" +import argparse,hashlib,json,pathlib,subprocess,sys +from decimal import Decimal +ROOT=pathlib.Path(__file__).resolve().parents[1] +FIXTURE_SHA='cb55604183a6db7df5fa968db283e7abeae794277d2b0cef033b0bdb92e615cb' + +def main(): + p=argparse.ArgumentParser(description=__doc__);p.add_argument('--output',required=True);p.add_argument('--scratch-path',default='.build');a=p.parse_args() + out=ROOT/a.output;out.mkdir(parents=True,exist_ok=True) + receipt=dict(status='failed',commands=[],scope='64 bounded grammar cases, not exhaustive arbitrary schemas; independent external oracle is a separate research receipt') + def run(name,cmd): + r=subprocess.run(cmd,cwd=ROOT,capture_output=True,text=True,timeout=1500) + text=r.stdout+r.stderr;log=out/(name+'.log');log.write_text(text) + receipt['commands'].append(dict(command=cmd,exit_code=r.returncode,log=log.name,sha256=hashlib.sha256(log.read_bytes()).hexdigest())) + if r.returncode:raise ValueError(name+' failed') + return text + try: + fixture=ROOT/'Tests/Fixtures/SchemaExamples/S3-G7-seeded-v1.json' + assert hashlib.sha256(fixture.read_bytes()).hexdigest()==FIXTURE_SHA + cases=json.loads(fixture.read_text())['cases'];ids={r['id'] for r in cases};assert len(ids)==len(cases)==64 + location=run('bin-path',['swift','build','--package-path','Examples/SchemaExamplesConsumer','--show-bin-path','--scratch-path',a.scratch_path]) + binary=pathlib.Path(location.strip())/'SchemaExamplesConsumer' + outputs=[] + expected={(i,p,s) for i in ids for p in ['minimal','demonstration'] for s in [0,1,42,2**64-1]} + for i in range(2): + text=run('process-'+str(i),[str(binary),'--g7-fuzz',str(fixture)]) + assert 'SCHEMA_EXAMPLES_G7 passed schemas=64 certified=512 invalid=64' in text.splitlines() + lines=[l.removeprefix('G7_RESULT ') for l in text.splitlines() if l.startswith('G7_RESULT ')] + rows=[json.loads(l,parse_float=Decimal) for l in lines] + assert len(rows)==512 and {(r['id'],r['profile'],r['seed']) for r in rows}==expected + assert all(0 Int {\n var completed = 0\n' + for record in records: + source += ' do {\n' + source += f' #sourceLocation(file: "{record["path"]}", line: {record["line"]})\n' + source += '\n'.join(' ' + line if line else '' for line in record['body'].splitlines()) + '\n' + source += ' #sourceLocation()\n completed += 1\n }\n' + source += f' precondition(completed == {SNIPPETS})\n return completed\n}}\n' + return source + + +def check(): + expected = consumer_source() + if CONSUMER.read_text() != expected: + raise ValueError('DocC Swift snippets differ from their compiled public consumer; run Scripts/schema_example_documentation.py --write and review') + records = [{k: v for k, v in row.items() if k != 'body'} for row in examples()] + return {'status': 'passed', 'snippets': SNIPPETS, 'guides': len({r['path'] for r in records}), + 'consumer': CONSUMER.relative_to(ROOT).as_posix(), + 'consumer_sha256': hashlib.sha256(expected.encode()).hexdigest(), 'sources': records} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--write', action='store_true') + args = parser.parse_args() + if args.write: + CONSUMER.write_text(consumer_source()) + print(json.dumps(check(), indent=2)) + + +if __name__ == '__main__': + main() diff --git a/Scripts/test_qualification_harness.py b/Scripts/test_qualification_harness.py index cbf9ad7..949484b 100644 --- a/Scripts/test_qualification_harness.py +++ b/Scripts/test_qualification_harness.py @@ -32,6 +32,28 @@ def test_composition_discovery_and_capacity_limits_are_not_blanket_successes(sel dict(report, unmet_capacity_targets=[]), dict(report, adoption_status='all_imports_qualified')]: self.assertFalse(check_result(command, 0, json.dumps(broken))[0]) + # Combined-source checks must retain both independently owned optional products. + for module, count, command in [ + ('JSONNavigationTests', 39, ['swift', 'test', '--filter', 'navigation']), + ('JSONSchemaExamplesTests', 98, ['swift', 'test', '--test-product', 'JSONSchemaExamplesTests']), + ]: + events = dict(bundle_counts=[count], module_function_counts={module: count}, + parameter_case_invocations=0) + self.assertTrue(check_result(command, 0, '', events)[0]) + for broken in [dict(events, bundle_counts=[count-1]), + dict(events, module_function_counts={'WrongModule': count}), + dict(events, parameter_case_invocations=1)]: + self.assertFalse(check_result(command, 0, '', broken)[0]) + policy = DISCOVERY + for key in ('root_functions', 'portable_root_functions', 'wasi_root_functions'): + self.assertEqual(policy[key]['JSONNavigationTests'], 39) + self.assertEqual(policy[key]['JSONSchemaExamplesTests'], 98) + self.assertEqual(policy[key]['JSONIntegrationTests'], 32) + for key in ('shared_state_functions', 'portable_shared_state_functions'): + self.assertEqual(policy[key]['JSONNavigationTests'], 1) + self.assertEqual(policy[key]['JSONSchemaExamplesTests'], 4) + self.assertEqual(policy[key]['JSONIntegrationTests'], 11) + def setUp(self): self.temporary = tempfile.TemporaryDirectory() self.addCleanup(self.temporary.cleanup) @@ -126,6 +148,12 @@ def test_exact_discovery_rejects_missing_duplicate_and_changed_parameters(self): self.assertTrue(check_result(harness, 0, 'HARNESS_TESTS passed cases=15')[0]) for stale in ['HARNESS_TESTS passed cases=10', 'HARNESS_TESTS passed cases=11', 'HARNESS_TESTS passed cases=12', 'HARNESS_TESTS passed cases=13', 'HARNESS_TESTS passed cases=14', '']: self.assertFalse(check_result(harness, 0, stale)[0]) + lifetime = ['python3', '-B', 'Scripts/qualify_schema_example_lifetime.py'] + marker = 'SCHEMA_EXAMPLES_CORPUS passed mode=owned scopes=128 values=6 exact=1' + self.assertTrue(check_result(lifetime, 0, marker)[0]) + self.assertFalse(check_result(lifetime, 1, marker)[0]) + for stale in ['', marker.replace('128', '1'), marker.replace('exact=1', 'exact=0')]: + self.assertFalse(check_result(lifetime, 0, stale)[0]) # Scoped portability lanes retain exact discovery and safety rather # than describing an exit-zero subset as full qualification. output = pathlib.Path(__file__).resolve().parents[1] / 'Artifacts/HarnessProbe' diff --git a/Scripts/verify_schema_example_documentation.py b/Scripts/verify_schema_example_documentation.py new file mode 100644 index 0000000..4f1867e --- /dev/null +++ b/Scripts/verify_schema_example_documentation.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Reject documentation drift/count/import/receipt mutations; verify all 49 scope rows.""" +import json +import pathlib +import shutil +import tempfile +from unittest.mock import patch + +import ci +import schema_example_documentation as docs + +ROOT = pathlib.Path(__file__).resolve().parents[1] + + +def main(): + rows = [] + assert docs.check()['snippets'] == 10 + rows.append({'case': 'actual-ten-snippets', 'accepted': True}) + source = (docs.CATALOG / 'VerifiedCandidates.md').read_text() + for name, change in [ + ('missing-block', lambda s: s.replace('```swift', '```text', 1)), + ('extra-block', lambda s: s + '\n```swift\nprecondition(true)\n```\n'), + ('empty-block', lambda s: s + '\n```swift\n\n```\n'), + ('foreign-import', lambda s: s.replace('import JSON\n', 'import Foundation\n', 1)), + ('changed-authored-code', lambda s: s.replace('result.attempts == 1', 'result.attempts == 2', 1)), + ('stale-consumer', lambda s: s), + ]: + with tempfile.TemporaryDirectory(prefix='swift-json-docs-') as temporary: + root = pathlib.Path(temporary) + catalog = root / 'Sources/JSONSchemaExamples/Documentation.docc' + shutil.copytree(docs.CATALOG, catalog) + (catalog / 'VerifiedCandidates.md').write_text(change(source)) + consumer = root / docs.CONSUMER.relative_to(ROOT) + consumer.parent.mkdir(parents=True) + consumer.write_text(docs.CONSUMER.read_text() + ('\n// stale\n' if name == 'stale-consumer' else '')) + with patch.object(docs, 'ROOT', root), patch.object(docs, 'CATALOG', catalog), patch.object(docs, 'CONSUMER', consumer): + try: + docs.check() + except ValueError: + rows.append({'case': name, 'accepted': False}) + else: + raise ValueError('Accepted documentation mutation: ' + name) + command = ['python3', '-B', 'Scripts/qualify_schema_examples.py'] + actual = 'SCHEMA_EXAMPLES_COMPILER passed public_cases=15 negatives=2\nSCHEMA_EXAMPLES_DOCUMENTATION_COMPILER passed snippets=10 processes=2' + for name, code, log, expected in [ + ('actual-receipt', 0, actual, True), + ('missing-documentation-outcome', 0, actual.splitlines()[0], False), + ('wrong-snippet-count', 0, actual.replace('snippets=10', 'snippets=11'), False), + ('nonzero-command', 1, actual, False), + ]: + accepted = ci.check_result(command, code, log)[0] + assert accepted == expected, name + rows.append({'case': name, 'accepted': accepted}) + policy = json.loads((ROOT / 'Documentation/Architecture/ResourceProfiles.json').read_text()) + metrics = [m.get('metric', m.get('id')) for k in ['core','schema','streaming','patch','enhancement_limits'] for m in policy[k]] + classified = [] + for line in (ROOT / 'Documentation/Architecture/Release1.6/SchemaExamplesLimits.md').read_text().splitlines(): + if not line.startswith('| ') or line.startswith(('| Metrics ', '| ---')): + continue + names = line.split('|')[1].strip().split(', ') + classified += names + assert len(classified) == len(set(classified)) == 49 and set(classified) == set(metrics), classified + assert len(rows) == 11 + print(json.dumps({'status': 'passed', 'checks': rows, 'scope_metrics': 49}, indent=2)) + print('SCHEMA_EXAMPLES_DOC_HARNESS passed cases=11 metrics=49') + + +if __name__ == '__main__': + main() diff --git a/Scripts/wasm_support.py b/Scripts/wasm_support.py index 102f0d0..ded0631 100644 --- a/Scripts/wasm_support.py +++ b/Scripts/wasm_support.py @@ -68,7 +68,7 @@ def binary(scratch, config, name): if preamble and not re.fullmatch(r"Another instance of SwiftPM .*waiting until that process has finished execution\.\.\.", preamble): raise ValueError('Unexpected manifest preamble: ' + preamble) manifest = json.loads(manifest_text[start:]) - if manifest['dependencies'] or sorted(p['name'] for p in manifest['products']) != ['JSON', 'JSONBridges', 'JSONHTML', 'JSONNavigation', 'JSONPatch', 'JSONSchema', 'JSONStreaming', 'swift-json']: + if manifest['dependencies'] or sorted(p['name'] for p in manifest['products']) != ['JSON', 'JSONBridges', 'JSONHTML', 'JSONNavigation', 'JSONPatch', 'JSONSchema', 'JSONSchemaExamples', 'JSONStreaming', 'swift-json']: raise ValueError('Changed public products/dependencies') def invoke(engine, path, mode='command', arguments=(), repo=True, resources=True): if engine == 'node': diff --git a/Sources/JSONCLIEngine/CLIBuildIdentity.swift b/Sources/JSONCLIEngine/CLIBuildIdentity.swift index c1d9c13..baabdd5 100644 --- a/Sources/JSONCLIEngine/CLIBuildIdentity.swift +++ b/Sources/JSONCLIEngine/CLIBuildIdentity.swift @@ -2,6 +2,6 @@ // Generated by Scripts/generate_cli_identity.py; excludes this file. // SHA-256 of the ordered Package.swift + Sources content manifest, not a Git commit. package enum CLIBuildIdentity { - package static let revision = "sha256:bba3e1b374e88d3df8fe4fd5e58423bdfb8982a085754f2d5ad6813830da83db" + package static let revision = "sha256:13096cf62cfd152b53c0c0dcd6292e5798904e6e3ac39b04f4f764322c89086c" package static let kind = "source-sha256" } diff --git a/Sources/JSONSchema/Compilation/JSONSchemaCompiler.swift b/Sources/JSONSchema/Compilation/JSONSchemaCompiler.swift index f1afd60..b9487d8 100644 --- a/Sources/JSONSchema/Compilation/JSONSchemaCompiler.swift +++ b/Sources/JSONSchema/Compilation/JSONSchemaCompiler.swift @@ -66,6 +66,12 @@ public struct JSONSchemaCompiler: Sendable { var state = JSONSchemaCompilation(options: options) return try state.compile(document, registry: registry, meter: &meter) } + /// Package composition preserves the admitted document and its source map. + package func compile(_ document: JSONSchemaDocument, registry: JSONSchemaRegistry, + meter: inout JSONWorkMeter) throws -> CompiledJSONSchema { + var state = JSONSchemaCompilation(options: options) + return try state.compile(document, registry: registry, meter: &meter) + } } /// Invocation-owned mutable graph builder. Every lookup remains exact and charged. diff --git a/Sources/JSONSchema/Compilation/JSONSchemaQualifiedNode.swift b/Sources/JSONSchema/Compilation/JSONSchemaQualifiedNode.swift new file mode 100644 index 0000000..3b84126 --- /dev/null +++ b/Sources/JSONSchema/Compilation/JSONSchemaQualifiedNode.swift @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON + +/// Read-only construction access to compiler-qualified declarations. Handles and +/// targets can only originate in the same immutable plan; no URI is reparsed here. +package struct JSONSchemaQualifiedNode: Sendable { + private let plan: JSONSchemaPlan + private let index: Int + fileprivate init(plan: JSONSchemaPlan, index: Int) { self.plan = plan; self.index = index } + + /// Plan-local identity, not a stable/public schema identifier. + package var identity: Int { index } + package var childCount: Int { plan.nodes[index].children.count } + package var referenceCount: Int { plan.nodes[index].references.count } + + package struct Declaration: Sendable { + package let raw: JSON + package let documentPointer: JSONPointer + package let resourceURI: JSONSchemaURI + package let vocabulary: JSONSchemaVocabulary + package let formatAssertion: Bool + } + package struct Child: Sendable { + package let keyword: JSONSchemaPlan.Keyword + package let token: JSONKey? + package let target: JSONSchemaQualifiedNode + } + package struct Reference: Sendable { + package let keyword: JSONSchemaPlan.Keyword + package let requiresDynamicScope: Bool + package let target: JSONSchemaQualifiedNode + } + package func declaration(meter: inout JSONWorkMeter) throws -> Declaration { + try meter.work() + let node = plan.nodes[index] + return .init(raw: node.raw, documentPointer: node.pointer, + resourceURI: plan.resources[node.resource].uri, + vocabulary: node.vocabulary, formatAssertion: node.formatAssertion) + } + package func child(at offset: Int, meter: inout JSONWorkMeter) throws -> Child { + try meter.work() + guard plan.nodes[index].children.indices.contains(offset) else { + throw JSONFailure(.configuration, code: .invalidOperation) + } + let edge = plan.nodes[index].children[offset] + return .init(keyword: edge.keyword, token: edge.token, + target: .init(plan: plan, index: edge.target)) + } + package func reference(at offset: Int, meter: inout JSONWorkMeter) throws -> Reference { + try meter.work() + guard plan.nodes[index].references.indices.contains(offset) else { + throw JSONFailure(.configuration, code: .invalidOperation) + } + let edge = plan.nodes[index].references[offset] + return .init(keyword: edge.keyword, requiresDynamicScope: edge.dynamicAnchor != nil, + target: .init(plan: plan, index: edge.target)) + } +} + +extension CompiledJSONSchema { + package var exampleRoot: JSONSchemaQualifiedNode { .init(plan: plan, index: plan.root) } +} diff --git a/Sources/JSONSchemaExamples/Documentation.docc/AdvancedConstruction.md b/Sources/JSONSchemaExamples/Documentation.docc/AdvancedConstruction.md new file mode 100644 index 0000000..7a594d9 --- /dev/null +++ b/Sources/JSONSchemaExamples/Documentation.docc/AdvancedConstruction.md @@ -0,0 +1,54 @@ +# Conservative advanced proposals + +Use bounded hints while keeping the complete native schema as the authority. + +```swift +import JSON +import JSONSchema +import JSONSchemaExamples + +let document = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"array","minItems":2,"uniqueItems":true,"items":{"type":"string"},"contains":{"pattern":"^SKU-[0-9]{4}$"},"minContains":2,"maxContains":2}"# +)) +let result = try JSONSchemaExampleGenerator(document: document).generate(seed: 42) +precondition(result.validation.isValid) +``` + +The synchronized public guide consumer and native tests execute this composed shape. Contains hints request enough items +and add the qualified contains child beside each applicable prefix/items child. +Several conjunctions remain separate. Full native validation counts actual matches, +checks maxima and propagates evaluated-item annotations. The constructor does not +solve arbitrary matching partitions or prevent every possible surplus match. + +The pattern helper only proposes fully anchored ASCII sequences of printable +literals, escaped regex punctuation, single ASCII ranges and exact `{n}` repeats. +It neither matches strings nor replaces the native regex engine. Unsupported +patterns can use finite admitted corpus/filler candidates. No Foundation regex, +external parser, unbounded fallback or new runtime dependency is introduced. + +## Honest incomplete search + +AllOf, oneOf, conditionals, dependencies, property-name/pattern rules and +unevaluated-location behavior are checked against the original root. Empty optional +objects or a later finite variation can satisfy some schemas; that does not +establish general constructive support for those keywords. A oneOf branch candidate +matching two branches is rejected and its evaluation work remains charged. + +Static external references require explicit immutable registry resources. Compiler +identities preserve base URI and anchor transitions. Reaching a scope-dependent +dynamic reference fails explicitly; omitting an optional dynamic child may yield a +valid finite witness without implementing constructive dynamic navigation. + +The G4 public consumer executes ten additional must-generate cases and eight +may-fail cases across two profiles/four seeds, recording every outcome. A may-fail +case may return a fully verified witness or explicit unsupported/exhausted result. +It cannot turn search failure into an unsatisfiability proof. Native resource +errors, compilation errors and cancellation remain operational failures. + +All scans, repeat digits, staging and completed false verdicts use the same finite +generation ledger. Returned statistics describe successful calls only; no value +or successful report is invented after a failure. Logical retained bytes are not +RSS and caller qualification work is separate. Algorithm is s3-candidates-v4; +the trace v2 grammar and source/corpus/policy v1 transports remain unchanged. +See for the qualified native lifecycle. G7/G8 final +platform/integration/release qualification remains separate. diff --git a/Sources/JSONSchemaExamples/Documentation.docc/DeterministicConstruction.md b/Sources/JSONSchemaExamples/Documentation.docc/DeterministicConstruction.md new file mode 100644 index 0000000..d916164 --- /dev/null +++ b/Sources/JSONSchemaExamples/Documentation.docc/DeterministicConstruction.md @@ -0,0 +1,87 @@ +# Deterministic construction + +Retain exact values, seed choices explicitly and identify every input to a decision. + +## Choose a profile and corpus + +```swift +import JSON +import JSONSchema +import JSONSchemaExamples + +let corpus = JSONSchemaExampleCorpus(values: [.string("example"), .null]) +let prepared = try JSONSchemaExampleGenerator( + document: JSONSchemaDocument(JSONParser().parse("true")), + options: .init(corpus: corpus) +) +let result = try prepared.generate(seed: 42, profile: .demonstration) +precondition(result.validation.isValid) +precondition(result.corpusDigest == prepared.corpusDigest) +precondition(result.randomDraws > 0) +``` + +The runnable public consumer exercises both profiles and a corpus with exact +large decimals and scalar-distinct keys. Minimal scans declared candidates in +stable order. Demonstration chooses an unbiased starting offset and scans +circularly, without replacement. It uses corpus tokens for true/empty schemas. +Neither profile claims to synthesize arbitrary constrained values or vary output +for every seed. Custom tokens never bypass native validation. + +## Exact construction + +The generator uses the existing iterative strict writer and parser with its one +invocation meter. Returned objects have scalar UTF-8 key order, numbers have +normalized exact spelling and arrays retain their nesting/order. Equivalent source +member orders and numeric spellings yield the same emitted bytes. NFC/NFD scalar +spellings remain distinct. No binary floating point or String dictionary intervenes. +Depth, value admission, construction, rejected choices and all native validations +consume cumulative work. Prepared plans can be shared; mutable PRNG and trace +state belong to each call. + +## Versioned identities + +`s3-candidates-v4` records `splitmix64-v1`, seed and profile. Snapshot, ordered +corpus, all policy caps/flags and complete abstract trace have independent versioned +SHA-256 identities. See the Release1.6/G2Determinism and G3Construction contracts for the byte formats. +These transports use compact scalar-key normalized-number strict JSON, not JCS +or unavailable original source bytes. Caller registry order is part of snapshot +identity. Policy identity includes all 49 metrics for each of generation, +compilation and evaluation; compilation assertion policy remains distinct. + +Trace records contain fixed codes and unsigned integers, including every raw +random draw and completed verdict. No arbitrary schema/instance text or diagnostic +message is logged. Every byte shares the cumulative diagnostic byte quota with +native diagnostics. A successful trace is complete; exhaustion/cancellation throws +without a certificate. Hash work and result metadata also fit before return. +Logical retained bytes do not describe process RSS, and owned work quotas do not +limit caller code. Native golden vectors and separate-process checks establish +the native G5 increment; full Linux/WASI/mobile qualification remains G7. + + +G3 adds conservative typed value requests to the trace and uses +`swift-json-example-trace-v2`. The original snapshot/corpus/policy transports stay +at v1. Historical G2 vectors retain their earlier IDs in source history and the +immutable G2 review archive. A new algorithm version can change witnesses/traces; +reproducibility requires the same implementation and versioned inputs. + +G4 extends proposals while retaining the trace v2 grammar; its envelope binds +s3-candidates-v4. Historical G3 witnesses/traces remain in the G3 source/archive. + +## Replay the same prepared inputs + +```swift +let prepared = try JSONSchemaExampleGenerator( + document: JSONSchemaDocument(JSONParser().parse(#"{"type":"string","minLength":3}"#)) +) +let first = try prepared.generate(seed: 42, profile: .demonstration) +let second = try prepared.generate(seed: 42, profile: .demonstration) +precondition(first.value == second.value && first.traceDigest == second.traceDigest) +precondition(first.statistics == second.statistics) +precondition(first.sourceSnapshotDigest == prepared.sourceSnapshotDigest) +precondition(first.policyDigest == prepared.policyDigest && first.corpusDigest == prepared.corpusDigest) +``` + +Retain the implementation commit as well as algorithm ID, seed/profile, source, +ordered registry, corpus and policies. Identical source digests alone do not bind +evaluator policies; equal seeds need not produce different values. There is no +locale, clock, system randomness, online lookup or process-randomized Hasher input. diff --git a/Sources/JSONSchemaExamples/Documentation.docc/JSONSchemaExamples.md b/Sources/JSONSchemaExamples/Documentation.docc/JSONSchemaExamples.md new file mode 100644 index 0000000..8742af2 --- /dev/null +++ b/Sources/JSONSchemaExamples/Documentation.docc/JSONSchemaExamples.md @@ -0,0 +1,44 @@ +# ``JSONSchemaExamples`` + +Deterministic exact JSON candidates certified by the existing offline schema validator. + +Use this optional 1.6 development library for schema-driven fixtures, examples and +tooling where every returned value must satisfy the original compiled root. Prepare +an admitted JSONSchemaDocument with explicit offline resources, then generate with +finite policies and a reproducible seed. JSON and JSONSchema keep their existing +semantics and acquire no dependency on this module. + +Construction is conservative: exact declared hints, bounded typed values, static +references and qualified composition, contains and anchored ASCII-pattern proposals. +Every candidate requires completed native root validation. Unsupported or exhausted +search is explicit; this is not a complete satisfiability solver. Formats, defaults, +requiredness, dynamic scope and annotations retain the native dialect's meanings. + +All Swift guide blocks execute through a separate public consumer. Native G1-G6 +incremental evidence is separate from remaining G7/G8 release/platform qualification. +The standalone source checkout is the API authority until this product is released. + +## Topics + +### Start with an offline schema + +- +- ``JSONSchemaExampleGenerator`` +- ``JSONSchemaExampleOptions`` +- ``JSONSchemaExampleResult`` + +### Construct exact, reproducible candidates + +- +- +- +- ``JSONSchemaExampleProfile`` +- ``JSONSchemaExampleCorpus`` + +### Bound work and handle failures + +- +- ``JSONSchemaExampleFailure`` +- ``JSONSchemaExampleUnsupportedFeature`` +- ``JSONSchemaUnsatisfiableProof`` +- ``JSONSchemaExampleConfigurationIssue`` diff --git a/Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md b/Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md new file mode 100644 index 0000000..5456120 --- /dev/null +++ b/Sources/JSONSchemaExamples/Documentation.docc/PoliciesAndLimits.md @@ -0,0 +1,107 @@ +# Policies, limits and lifecycle + +Keep preparation separate, bound every generation attempt cumulatively and treat errors as terminal. + +## Configure finite budgets and read remaining work + +``JSONSchemaExampleOptions/compilationOptions`` governs the whole preparation: +compilation plus source, corpus and policy serialization/hashing. Generation uses +the per-metric intersection of ``JSONSchemaExampleOptions/limits`` and +``JSONSchemaExampleOptions/validationOptions`` limits. Both default to balanced; +maximumAttempts defaults to 16 and counts completed invalid validations too. + +```swift +import JSON +import JSONSchema +import JSONSchemaExamples + +var limits = JSONLimits.conservative +limits[.workUnits] = 2_000_000 +var evaluation = JSONSchemaOptions(limits: limits) +evaluation.collectAnnotations = true +let options = JSONSchemaExampleOptions( + limits: limits, maximumAttempts: 4, validationOptions: evaluation +) +let prepared = try JSONSchemaExampleGenerator( + document: JSONSchemaDocument(JSONParser().parse(#"{"enum":[0,"ok"],"type":"string"}"#)), + options: options +) +let result = try prepared.generate() +let ceiling = min(prepared.options.limits[.workUnits], + prepared.options.validationOptions.limits[.workUnits]) +let used = result.statistics[.workUnits] +precondition(used <= ceiling) +let remainingWork = ceiling - used +precondition(remainingWork < ceiling && result.attempts == 2) +precondition(result.statistics[.evaluationWorkUnits] > 0) +``` + +Discovery, construction, normalization, every native validation and final trace +hashing share one invocation meter. False verdicts keep their evaluation/numeric/ +regex work and issue/diagnostic charges. Subsystem work is already included in +workUnits: do not subtract it again. Work, numeric work, evaluation work and +aggregateRegexWorkUnits never reset between candidates or branches. A separate +public generate call starts an independent invocation, not a continuation of a +previous report. To bound a larger caller composition, carry checked remaining +work and subsystem ceilings across its successful calls. PreparationStatistics is +separate and must fit its preparation policy; do not add memory peaks together. + +## Preserve evaluator policy and raw metadata + +Format is annotation by default. The generator forwards explicit validation options +unchanged; a required format-assertion vocabulary remains authoritative. Compilation +assertion policy is separately retained. Defaults and examples only offer candidates. +The generator neither fills defaults nor upgrades raw metadata into constraints. + +```swift +let document = try JSONSchemaDocument(JSONParser().parse( + #"{"const":"not-an-email","format":"email","default":"example@example.com"}"# +)) +let annotated = try JSONSchemaExampleGenerator(document: document).generate() +precondition(annotated.value == .string("not-an-email")) +precondition(!annotated.validationOptions.formatAssertion) +let plan = try JSONSchemaCompiler().compile(document) +let asserted = try plan.validate(annotated.value, options: .init(formatAssertion: true)) +precondition(!asserted.isValid) +precondition(document.raw.objectValue?["default"] == .string("example@example.com")) +precondition(!annotated.compilationOptions.formatAssertion) +``` + +Do not combine a result with another plan, registry or policy and imply the original +certificate applies. Independent validation above is application verification work, +separate from the completed generation report. + +## Capacity, retention and terminal state + +All 49 existing profile entries stay finite. Individual strings, numeric tokens, +containers, depth and active frames have capacity ceilings. Per-materialization +input/output/node/text capacities retire between candidates while report totals and +charged work are preserved. Per-search regex work has its existing search ceiling; +aggregate regex work remains cumulative. Compilation-only, streaming and Patch +metrics do not imply that generation performs those operations. No new unlimited +setting or generated-node metric is introduced. + +``JSONSchemaExampleResult/statistics`` snapshots certification, including the returned +value/verdict, metadata and conservative staging reservations. Some scratch reservations +live until the proposal or invocation ends even when Swift can release a temporary +earlier. Rejected ownership is released without refunding work. Remaining invocation +reservations end on result transfer or failure. Prepared source/registry/corpus/plan +ownership lasts as long as the caller retains the immutable generator. A returned +value remains usable after preparation leaves scope; it holds no generator or search stack. + +Logical retained bytes and peakLogicalRetainedBytes are accounting weights, not heap +size, allocator reclamation timing or RSS. Owned work quotas do not cap arbitrary +caller callbacks or instructions and are not wall-clock deadlines. + +Native operational errors and cancellation immediately end the invocation. Cleanup +preserves the first terminal failure and consumed counters. No partial candidate or +successful report escapes; retrying with a fresh full quota would lose a caller's +cumulative bound. Cancellation is checked at entry, before owned growth and inside +numeric, regex, traversal and hashing work. There is no public callback or detached +background generator. Independent calls can safely reuse immutable preparation. + +A successful abstract trace is complete. Its bytes share diagnosticBytes with +native diagnostics; exhaustion throws instead of returning a truncated certificate. +Trace hashing includes its actual 64-byte hex result. The G5 correction removed an +unmatched 32-byte refund without changing the algorithm, transport or default caps. +Platform-wide release qualification and controlled performance remain separate gates. diff --git a/Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md b/Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md new file mode 100644 index 0000000..0243a9a --- /dev/null +++ b/Sources/JSONSchemaExamples/Documentation.docc/TypedConstruction.md @@ -0,0 +1,93 @@ +# Conservative typed construction + +Construct a bounded proposal, then certify the complete original schema. + +```swift +import JSON +import JSONSchema +import JSONSchemaExamples + +let document = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"integer","minimum":9007199254740993}"# +)) +let generator = try JSONSchemaExampleGenerator(document: document) +let result = try generator.generate(seed: 42) +precondition(result.validation.isValid) +``` + +The standalone public consumer's G3 mode runs this shape and all 24 foundational +must-generate fixtures with both profiles and four seeds. It independently +validates and roundtrips all 192 values, checks 90 witness expectations and keeps +two trivial unsatisfiability proofs separate. Its caller I/O/assertions are +qualification work, separate from each generator invocation's cumulative meter. + +## Exact scalars and containers + +Basic numeric lower/upper/open bounds and multipleOf use exact bounded decimal +steps and signed rounding. Integers larger than UInt64 remain exact. There is no +Double, Foundation Decimal or epsilon intermediary. Dense digit alignment can +exhaust a finite quota; this is not a general constraint solver. + +Strings count Unicode scalars. Demonstration can use immutable synthetic tokens; +known asserted email uses a fixed example.com address. Format metadata never +implicitly asserts a format or changes the declared type. Required keys are +preserved as exact scalar sequences, including simultaneous NFC/NFD spellings. +MinProperties can fill declared or synthetic keys. Each conjunction contributes +its own property/additional constraints, with no dictionary override or inferred +requiredness. Arrays preserve nesting, use positional prefix/items declarations +and bounded size. Uniqueness uses finite leaf variations and native certification. + +## References, composition and incomplete search + +Static reference targets come from the prepared compiler plan. Positive allOf +constraints remain separate conjunctions. AnyOf/oneOf declarations provide finite +alternative proposals, never branch-local certificates. Repeated DAG targets are +shared constraints; a same-instance ancestor reference cycle is unsupported. +Recursive containers can use an admitted empty base case. No independent URI +resolver, schema algebra, default repair or alternate validator is introduced. + +Construction uses an explicit frame stack and the same invocation meter through +staging, comparisons, exact arithmetic, false verdicts and final trace hashing. +Operational error/cancellation terminates the call. Returned logical accounting +is not process RSS or caller instruction accounting. Required defaults/examples +can be invalid; an independently constructed proposal may later pass while the +original metadata remains unchanged. + +## Supported construction subset + +| Declarations | Current proposal behavior | Boundary | +| --- | --- | --- | +| const, enum, default, examples | Exact declared candidates, arrays in declaration order | Defaults/examples can be invalid; no repair | +| type and unions | Fixed kind order and qualified intersections | No universal witness promise | +| minimum/maximum, exclusive bounds, multipleOf | Bounded exact decimal stepping/rounding | Several divisors or dense alignment can fail or exhaust limits | +| minLength/maxLength | Scalar-counted corpus or filler strings | Byte caps also apply; no implicit Unicode normalization | +| required/properties/minProperties/maxProperties | Exact required keys, declared or synthetic names | No general pattern/name/dependency key synthesis | +| prefixItems/items/minItems/maxItems/uniqueItems | Positional typed proposals and finite variations | Uniqueness can exhaust search | +| contains/minContains/maxContains | Minimum matching-position hints | Full native evaluation counts all matches and maxima | +| Anchored literal/range/exact-repeat patterns | Bounded ASCII proposals | No general regex inversion; native regex certifies | +| Static refs, allOf, anyOf, oneOf | Qualified targets, separate conjunctions and finite hints | No schema merging or branch-local certification | +| if/then/else, dependencies, not, unevaluated locations | Native certification of offered candidates | No general constructive solver | +| Dynamic references and cycles | Optional omission or finite base case can succeed | Reached dynamic scope/active same-instance cycles fail explicitly | + +The machine-readable Release1.6/Capabilities ledger classifies all 59 compiled +keywords; it describes construction hints, not changes to Draft 2020-12 evaluation. +A validator-supported keyword is not necessarily constructively supported. G3/G4 +fixture success applies to the pinned cases, not every schema using those keywords. + +## Preserve exact keys and requiredness + +```swift +let document = try JSONSchemaDocument(JSONParser().parse( + #"{"type":"object","required":["é","e\u0301"],"properties":{"é":{"const":9007199254740993},"e\u0301":{"const":null},"optional":{"default":42}}}"# +)) +let result = try JSONSchemaExampleGenerator(document: document).generate() +let object = result.value.objectValue +let exact = try JSONNumber(parsing: "9007199254740993") +precondition(object?.count == 2 && object?["é"]?.numberValue == exact) +precondition(object?["e\u{301}"] == .null && object?["optional"] == nil) +``` + +Both scalar-distinct keys survive. Null is present; the optional default is not +inserted. See for cumulative work and retention semantics. +G5 native lifecycle qualification is complete; final platform/integration/release +qualification remains G7/G8. diff --git a/Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md b/Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md new file mode 100644 index 0000000..f30b4ed --- /dev/null +++ b/Sources/JSONSchemaExamples/Documentation.docc/VerifiedCandidates.md @@ -0,0 +1,95 @@ +# Verified candidates + +Prepare an offline schema once and return exact JSON only after native validation succeeds. + +## Prepare and generate + +Add the optional JSONSchemaExamples product to your application target, then import +JSON, JSONSchema and JSONSchemaExamples explicitly. This product is in 1.6 development; +use the inspected feature checkout until a qualified release includes it. + +```swift +import JSON +import JSONSchema +import JSONSchemaExamples + +let document = try JSONSchemaDocument(JSONParser().parse( + #"{"const":9007199254740993}"# +)) +let prepared = try JSONSchemaExampleGenerator(document: document) +let result = try prepared.generate(seed: 42, profile: .minimal) +let text = try JSONWriter().writeString(result.value) +precondition(result.validation.isValid && text == "9007199254740993") +precondition(result.attempts == 1 && result.statistics[.evaluationWorkUnits] > 0) +``` + +``JSONSchemaExampleGenerator`` retains the admitted document, explicit registry, +compiled plan and policies. Each call owns independent mutable state. A successful +``JSONSchemaExampleResult`` contains its exact value, actual native verdict, +cumulative statistics and versioned provenance. Callers cannot initialize a +certificate through a public initializer. The existing JSON model and +JSONSchemaCompiler/JSONSchemaValidator remain the semantic authorities. + +## Select a definition and supply offline resources + +A document containing only `$defs` does not select an instance schema. Root the +requested definition explicitly. Retrieval URIs identify resources; they never +cause network access. Compilation uses only packaged meta-schemas and your registry. + +```swift +let parser = JSONParser() +let resource = try JSONSchemaDocument(parser.parse( + #"{"$defs":{"count":{"type":"integer","minimum":9007199254740993}}}"# +), retrievalURI: JSONSchemaURI("https://example.com/schemas/counts")) +let registry = try JSONSchemaRegistry(documents: [resource]) +let root = try JSONSchemaDocument(parser.parse( + ##"{"$ref":"https://example.com/schemas/counts#/$defs/count"}"## +)) +let prepared = try JSONSchemaExampleGenerator(document: root, registry: registry) +let result = try prepared.generate() +let plan = try JSONSchemaCompiler().compile(root, registry: registry) +let independentlyValid = try plan.validate(result.value).isValid +let exact = try JSONNumber(parsing: "9007199254740993") +let roundtrip = try JSONParser().parse(JSONWriter().write(result.value)) +precondition(independentlyValid && result.value.numberValue == exact) +precondition(roundtrip == result.value) +``` + +The final verdict always evaluates the original prepared root, including reference +siblings. Raw `$defs`, defaults, examples and unknown metadata are retained without +inserting fields, repairing candidates or inferring requiredness. Static targets +come from the compiler-qualified plan; construction does not implement another URI +resolver. Reached dynamic scope and active same-instance cycles can fail explicitly. + +## Handle incomplete search separately from errors + +A rejected candidate is an ordinary completed false verdict and consumes budget. +An exhausted search can still have a valid witness outside the offered candidates. +Only root false and an active root `not` of true/empty schema have trivial proofs. +No search exhaustion, reference cycle or operational error establishes unsatisfiability. + +```swift +let document = try JSONSchemaDocument(JSONParser().parse( + #"{"const":1,"type":"string"}"# +)) +let prepared = try JSONSchemaExampleGenerator(document: document) +var exhausted = false +do { + _ = try prepared.generate() +} catch JSONSchemaExampleFailure.searchExhausted(let attempts) { + precondition(attempts == 1) + exhausted = true +} +precondition(exhausted) +``` + +Other construction outcomes use ``JSONSchemaExampleFailure/unsupportedFeature(_:)``, +``JSONSchemaExampleFailure/provenUnsatisfiable(_:)`` or +``JSONSchemaExampleFailure/invalidConfiguration(_:)``. Native compilation/evaluation +errors, resource errors and CancellationError propagate separately; catch them only +when the application can handle them. A failure has no value or successful work report. + +See , , +and for the bounded construction and accounting contracts. +Every Swift block in these guides is synchronized with and executed by the standalone +Examples/SchemaExamplesConsumer package using public imports only. diff --git a/Sources/JSONSchemaExamples/JSONSchemaExampleCorpus.swift b/Sources/JSONSchemaExamples/JSONSchemaExampleCorpus.swift new file mode 100644 index 0000000..eb3b279 --- /dev/null +++ b/Sources/JSONSchemaExamples/JSONSchemaExampleCorpus.swift @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON + +/// Immutable ordered synthetic candidates, admitted when a generator is prepared. +/// +/// Tokens never override schema semantics. The demonstration profile uses them +/// for true/empty schemas and conservative string proposals. Corpus order and +/// exact admitted contents contribute to deterministic identity. Preparation +/// checks all tokens with its finite policy; storing the corpus alone is unmetered. +public struct JSONSchemaExampleCorpus: Sendable { + /// Exact ordered values; order contributes to corpus identity and seeded choices. + public let values: [JSON] + /// Stores caller-authored values without admission or implicit numeric conversion. + /// - Parameter values: A nonempty corpus checked under preparation limits later. + public init(values: [JSON]) { self.values = values } + /// Version 1 synthetic corpus: null, false, zero, "example", empty array and empty object. + /// Materializes values for the caller's lifetime instead of retaining JSON payloads globally. + /// As with caller-authored corpora, preparation performs the checked admission later. + public static var synthetic: Self { + Self(values: [.null, .boolean(false), 0, .string("example"), .array([]), .object(.init(empty: ()))]) + } +} diff --git a/Sources/JSONSchemaExamples/JSONSchemaExampleGenerator.swift b/Sources/JSONSchemaExamples/JSONSchemaExampleGenerator.swift new file mode 100644 index 0000000..a31815b --- /dev/null +++ b/Sources/JSONSchemaExamples/JSONSchemaExampleGenerator.swift @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema + +/// An immutable, offline source snapshot with invocation-owned candidate search. +/// +/// Declared hints and conservative typed construction share one cumulative ledger. +/// Every candidate is validated against the original compiled root. Dynamic scope +/// and difficult interdependent constraints can still fail explicitly. Preparation +/// has its own finite ledger; each generate call is independent and shares one +/// cumulative ledger through all candidate checks. See +/// and for supported construction and lifecycle boundaries. +public struct JSONSchemaExampleGenerator: Sendable { + /// The admitted source document, including any caller-supplied source map. + public let document: JSONSchemaDocument + /// The explicit immutable offline registry used during compilation and discovery. + public let registry: JSONSchemaRegistry + /// The frozen preparation, generation and evaluation policies. + public let options: JSONSchemaExampleOptions + /// The normalized source/registry snapshot digest; policies and corpus have separate identities. + public let sourceSnapshotDigest: String + /// The single preparation ledger, covering compilation, serialization and hashing. + public let preparationStatistics: JSONOperationStatistics + /// SHA-256 of the versioned, admitted ordered corpus transport. + public let corpusDigest: String + /// SHA-256 binding all generation, compilation and evaluation policies. + public let policyDigest: String + private let corpus: [JSON] + private let compiled: CompiledJSONSchema + + /// Compiles once and prepares a bounded source snapshot without online resolution. + /// - Parameters: + /// - document: The already admitted root schema. + /// - registry: Explicit immutable resources, including any custom meta-schemas. + /// - options: Finite preparation/search/evaluation policies retained unchanged. + /// - Throws: Generator configuration, native compilation/resource errors or cancellation. + public init(document: JSONSchemaDocument, registry: JSONSchemaRegistry = .empty, + options: JSONSchemaExampleOptions = .init()) throws { + try Task.checkCancellation() + guard options.maximumAttempts > 0, options.maximumAttempts < UInt64.max else { + throw JSONSchemaExampleFailure.invalidConfiguration(.attemptLimit) + } + guard !options.corpus.values.isEmpty else { + throw JSONSchemaExampleFailure.invalidConfiguration(.emptyCorpus) + } + try options.limits.validate(); try options.validationOptions.limits.validate() + var meter = try JSONWorkMeter(limits: options.compilationOptions.limits, domain: .schemaCompilation) + let compiled = try JSONSchemaCompiler(options: options.compilationOptions) + .compile(document, registry: registry, meter: &meter) + let digest = try SchemaExampleDigests.snapshot(document: document, registry: registry, meter: &meter) + let normalizedCorpus = try SchemaExampleExact.construct(.array(options.corpus.values), meter: &meter) + guard let tokens = normalizedCorpus.arrayValue else { throw JSONFailure(.configuration, code: .invalidOperation) } + corpus = tokens + corpusDigest = try SchemaExampleDigests.corpus(tokens, meter: &meter) + policyDigest = try SchemaExampleDigests.policy(options, meter: &meter) + self.document = document; self.registry = registry; self.options = options + self.compiled = compiled; sourceSnapshotDigest = digest + preparationStatistics = meter.statistics() + } + + /// Offers bounded exact candidates, returning only a completed native valid verdict. + /// + /// False candidate verdicts retain their work and diagnostic charges. An + /// operational error or cancellation ends the invocation without a candidate + /// or successful report. Reusing this prepared generator starts an independent + /// invocation; it does not continue an earlier call's remaining budget. + /// - Parameters: + /// - seed: SplitMix64 initial state for demonstration choices; always bound into the trace. + /// - profile: Stable minimal order or seeded demonstration candidate selection. + /// - Returns: The exact certified value and cumulative successful-invocation statistics. + /// - Throws: Explicit construction failure, native operational error or cancellation. + public func generate(seed: UInt64 = 0, profile: JSONSchemaExampleProfile = .minimal) throws -> JSONSchemaExampleResult { + let limits = try options.limits.intersection(options.validationOptions.limits) + var meter = try JSONWorkMeter(limits: limits, domain: .core) + return try generate(seed: seed, profile: profile, meter: &meter) + } + + // Internal qualification seam, also used by the public entry. The caller must + // supply the actual effective policy; no fresh meter or mutable callback is + // introduced inside search. Successful statistics describe certification, + // before ownership transfers to the caller and invocation reservations end. + func generate(seed: UInt64 = 0, profile: JSONSchemaExampleProfile = .minimal, + meter: inout JSONWorkMeter) throws -> JSONSchemaExampleResult { + try meter.checkpoint() + let retained = meter.used(.retainedBytes), annotations = meter.used(.annotationBytes) + let regex = meter.used(.regexRetainedBytes) + do { + guard meter.limits == (try options.limits.intersection(options.validationOptions.limits)), + meter.domain == .core else { throw JSONFailure(.configuration, code: .invalidOperation) } + let result = try search(seed: seed, profile: profile, meter: &meter) + try endScope(retained: retained, annotations: annotations, regex: regex, meter: &meter) + return result + } catch { + // Includes evaluator errors that do not originate in the meter and + // incomplete search. Cleanup is allowed after cancellation/exhaustion; + // it cannot refund work or clear the original terminal health. + meter.recordFailure(error) + try endScope(retained: retained, annotations: annotations, regex: regex, meter: &meter) + throw error + } + } + + private func endScope(retained: UInt64, annotations: UInt64, regex: UInt64, + meter: inout JSONWorkMeter) throws { + let (bytes, badBytes) = meter.used(.retainedBytes).subtractingReportingOverflow(retained) + let (annotationBytes, badAnnotations) = meter.used(.annotationBytes).subtractingReportingOverflow(annotations) + let (regexBytes, badRegex) = meter.used(.regexRetainedBytes).subtractingReportingOverflow(regex) + guard !badBytes, !badAnnotations, !badRegex else { throw JSONFailure(.configuration, code: .invalidOperation) } + try meter.releaseAnnotationState(annotationBytes) + try meter.releaseRegexState(regexBytes) + try meter.release(bytes) + } + + private func search(seed: UInt64, profile: JSONSchemaExampleProfile, + meter: inout JSONWorkMeter) throws -> JSONSchemaExampleResult { + var random = SchemaExampleRandom(seed: seed) + var trace = try SchemaExampleTrace(snapshot: sourceSnapshotDigest, corpus: corpusDigest, + policy: policyDigest, seed: seed, profile: profile, meter: &meter) + let root = compiled.exampleRoot + let declaration = try root.declaration(meter: &meter) + if declaration.raw.booleanValue == false { + throw JSONSchemaExampleFailure.provenUnsatisfiable(.falseSchema) + } + if declaration.vocabulary.applies(.not), + let negated = try declaration.raw.objectValue?.lookup("not", meter: &meter), + negated.booleanValue == true || negated.objectValue?.isEmpty == true { + throw JSONSchemaExampleFailure.provenUnsatisfiable(.notTrue) + } + try meter.retain(JSONLogicalWeights.frame) + var pending = [root], visited: [Int] = [], constructionSeeds: [JSONSchemaQualifiedNode] = [] + var attempts: UInt64 = 0 + while let node = pending.popLast() { + try meter.checkpoint(); try meter.work(); try meter.release(JSONLogicalWeights.frame) + var seen = false + for identity in visited { try meter.work(); if identity == node.identity { seen = true; break } } + if seen { continue } + try meter.charge(.schemaNodes) + try meter.retain(JSONLogicalWeights.indexSlot); visited.append(node.identity) + try trace.event(1, 0, 0, meter: &meter) + try meter.retain(JSONLogicalWeights.indexSlot); constructionSeeds.append(node) + let view = try node.declaration(meter: &meter) + for offset in 0..= traceBytes else { throw JSONFailure(.configuration, code: .invalidOperation) } + try meter.release(delta - traceBytes) + } + } + if attempts == 0 { throw JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction) } + throw JSONSchemaExampleFailure.searchExhausted(attempts: attempts) + } + + private func scan(_ values: [JSON], role: UInt64, seed: UInt64, profile: JSONSchemaExampleProfile, + attempts: inout UInt64, random: inout SchemaExampleRandom, + trace: inout SchemaExampleTrace, meter: inout JSONWorkMeter) throws -> JSONSchemaExampleResult? { + guard !values.isEmpty else { return nil } + var index = profile == .minimal ? 0 : Int(try random.uniform(upperBound: UInt64(values.count), trace: &trace, meter: &meter)) + for _ in values.indices { + try meter.work() + try trace.event(2, role, UInt64(index), meter: &meter) + if let result = try certify(values[index], seed: seed, profile: profile, attempts: &attempts, + random: &random, trace: &trace, meter: &meter) { return result } + // Avoid start + offset overflow on 32-bit WASI. + index = index == values.count - 1 ? 0 : index + 1 + } + return nil + } + + private func certify(_ value: JSON, seed: UInt64, profile: JSONSchemaExampleProfile, + attempts: inout UInt64, random: inout SchemaExampleRandom, + trace: inout SchemaExampleTrace, meter: inout JSONWorkMeter) throws -> JSONSchemaExampleResult? { + try meter.checkpoint() + guard attempts < options.maximumAttempts else { throw JSONSchemaExampleFailure.searchExhausted(attempts: attempts) } + // The configured ceiling is strictly below UInt64.max, so this cannot wrap. + attempts += 1 + try meter.resetValueBounds() + let retainedBefore = meter.used(.retainedBytes) + let annotationBefore = meter.used(.annotationBytes) + let value = try SchemaExampleExact.construct(value, meter: &meter) + try meter.resetValueBounds() + try meter.operationDomain(.schemaEvaluation) + let verdict = try JSONSchemaValidator(options: options.validationOptions).validate(value, against: compiled, meter: &meter) + try meter.operationDomain(.core) + guard verdict.isValid else { + // Nothing from this verdict escapes. Release only this attempt's live + // ownership; work, diagnostics and lifetime admission counts stay charged. + let (retained, badRetained) = meter.used(.retainedBytes).subtractingReportingOverflow(retainedBefore) + let (annotations, badAnnotations) = meter.used(.annotationBytes).subtractingReportingOverflow(annotationBefore) + guard !badRetained, !badAnnotations else { throw JSONFailure(.configuration, code: .invalidOperation) } + try meter.releaseAnnotationState(annotations) + try meter.release(retained) + try trace.event(4, attempts, 0, meter: &meter) + return nil + } + try trace.event(4, attempts, 1, meter: &meter) + try trace.event(5, attempts, random.draws, meter: &meter) + let events = trace.events + let traceDigest = try trace.digest(meter: &meter) + try meter.retain(512); try meter.checkpoint() + return .init(value: value, validation: verdict, seed: seed, profile: profile, + generatorVersion: SchemaExampleVersion.generator, sourceSnapshotDigest: sourceSnapshotDigest, + schemaDigestAlgorithm: SchemaExampleVersion.snapshot, + corpusDigestAlgorithm: SchemaExampleVersion.corpus, corpusDigest: corpusDigest, + policyDigestAlgorithm: SchemaExampleVersion.policy, policyDigest: policyDigest, + rngAlgorithm: SchemaExampleVersion.rng, randomDraws: random.draws, + traceDigestAlgorithm: SchemaExampleVersion.trace, traceDigest: traceDigest, traceEventCount: events, + validationOptions: options.validationOptions, compilationOptions: options.compilationOptions, + attempts: attempts, statistics: meter.statistics()) + } +} diff --git a/Sources/JSONSchemaExamples/JSONSchemaExampleOptions.swift b/Sources/JSONSchemaExamples/JSONSchemaExampleOptions.swift new file mode 100644 index 0000000..ebc6165 --- /dev/null +++ b/Sources/JSONSchemaExamples/JSONSchemaExampleOptions.swift @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema + +/// Finite preparation, search and validation policies for an offline generator. +public struct JSONSchemaExampleOptions: Sendable { + /// Generation capacities; their intersection with validation limits governs each invocation. + public var limits: JSONLimits + /// The positive finite ceiling on completed candidate validations, including invalid candidates. + /// Defaults to 16; zero and UInt64.max are rejected at preparation. It is not + /// a completeness guarantee: unsupported construction may stop before this ceiling. + public var maximumAttempts: UInt64 + /// Immutable ordered candidates admitted and hashed during preparation. + public var corpus: JSONSchemaExampleCorpus + /// One compilation/snapshot/corpus/policy preparation budget, separate from generation. + /// Its format-assertion setting remains distinct from evaluation policy. + public var compilationOptions: JSONSchemaOptions + /// Unchanged evaluator policy; required vocabulary assertions remain authoritative. + /// Its limits intersect generation limits, while output, diagnostics, annotations + /// and explicit format assertion are forwarded unchanged to native validation. + public var validationOptions: JSONSchemaOptions + + /// Creates policies checked when the generator is prepared. + /// - Parameters: + /// - limits: Finite generation capacities and cumulative quotas. + /// - maximumAttempts: A positive value strictly below UInt64.max. + /// - corpus: Exact synthetic tokens; never authoritative over schema constraints. + /// - compilationOptions: Offline compilation and preparation limits/policies. + /// - validationOptions: Instance evaluation limits and diagnostic/format policies. + public init(limits: JSONLimits = .balanced, maximumAttempts: UInt64 = 16, + corpus: JSONSchemaExampleCorpus = .synthetic, + compilationOptions: JSONSchemaOptions = .init(), + validationOptions: JSONSchemaOptions = .init()) { + self.limits = limits; self.maximumAttempts = maximumAttempts; self.corpus = corpus + self.compilationOptions = compilationOptions; self.validationOptions = validationOptions + } +} + +/// A candidate preference, without a guarantee of globally minimal output. +public enum JSONSchemaExampleProfile: Sendable { + /// Prefer stable declared candidates and small conservative typed values. + case minimal + /// Seeded declared-candidate scans and synthetic tokens for admitted typed strings. + case demonstration +} + +/// A construction feature not implemented by this algorithm version. +public enum JSONSchemaExampleUnsupportedFeature: Sendable, Equatable { + /// No supported declaration supplied a candidate; this does not establish unsatisfiability. + case candidateConstruction + /// Construction would need evaluation-scope-aware dynamic reference navigation. + case dynamicReference + /// Construction revisited an active same-instance ancestor without progress. + case cyclicReference +} + +/// A sound trivial root-schema proof, distinct from incomplete search. +public enum JSONSchemaUnsatisfiableProof: Sendable, Equatable { + /// The original root is the Boolean false schema. + case falseSchema + /// The active root applicator negates true or an empty object schema. + case notTrue +} + +/// An invalid generator-specific configuration, without raw schema content. +public enum JSONSchemaExampleConfigurationIssue: Sendable, Equatable { + /// maximumAttempts was zero or UInt64.max. + case attemptLimit + /// The corpus had no candidates. + case emptyCorpus +} + +/// Bounded construction failures; core operational errors and cancellation propagate separately. +public enum JSONSchemaExampleFailure: Error, Sendable, Equatable { + /// A construction feature is unsupported, without a validity verdict. + /// - Parameter feature: The stable unsupported-feature code. + case unsupportedFeature(_ feature: JSONSchemaExampleUnsupportedFeature) + /// All offered candidates failed validation or the attempt ceiling was reached. + /// - Parameter attempts: The number of completed candidate validations. + case searchExhausted(attempts: UInt64) + /// A reviewed trivial proof establishes that the original root cannot accept any instance. + /// - Parameter proof: The sound proof kind; never inferred from exhaustion. + case provenUnsatisfiable(_ proof: JSONSchemaUnsatisfiableProof) + /// Generator configuration was invalid before compilation or search. + /// - Parameter issue: The stable configuration code. + case invalidConfiguration(_ issue: JSONSchemaExampleConfigurationIssue) +} diff --git a/Sources/JSONSchemaExamples/JSONSchemaExampleResult.swift b/Sources/JSONSchemaExamples/JSONSchemaExampleResult.swift new file mode 100644 index 0000000..4bd7108 --- /dev/null +++ b/Sources/JSONSchemaExamples/JSONSchemaExampleResult.swift @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema + +/// A candidate certified by completed native validation against the prepared root. +/// +/// Only the generator initializes results. The current algorithm offers declared hints, static +/// references and conservative typed construction, not arbitrary satisfiability decisions. +public struct JSONSchemaExampleResult: Sendable { + /// The exact candidate that received the attached valid verdict. + public let value: JSON + /// The native validator's completed valid result, including its actual diagnostics/annotations. + public let validation: JSONValidationResult + /// The caller's SplitMix64 seed, bound into the deterministic trace. + public let seed: UInt64 + /// The requested candidate preference. + public let profile: JSONSchemaExampleProfile + /// The versioned candidate algorithm, currently s3-candidates-v4. + public let generatorVersion: String + /// SHA-256 of the documented normalized source/registry transport, not original text or JCS. + public let sourceSnapshotDigest: String + /// The transport and hashing identity, currently swift-json-schema-snapshot-v1. + public let schemaDigestAlgorithm: String + /// The versioned ordered corpus transport identity. + public let corpusDigestAlgorithm: String + /// SHA-256 of the prepared exact corpus, including token order. + public let corpusDigest: String + /// The versioned all-caps policy transport identity. + public let policyDigestAlgorithm: String + /// SHA-256 binding generation, compilation and validation configuration. + public let policyDigest: String + /// The random algorithm identity, splitmix64-v1, even when no draws were needed. + public let rngAlgorithm: String + /// All raw draws, including those rejected by unbiased bounded selection. + public let randomDraws: UInt64 + /// The versioned complete decision transport identity. + public let traceDigestAlgorithm: String + /// SHA-256 of every bounded abstract decision through the final valid verdict. + public let traceDigest: String + /// The complete event count; trace exhaustion throws rather than truncating. + public let traceEventCount: UInt64 + /// The exact instance evaluation policy used for certification. + public let validationOptions: JSONSchemaOptions + /// The separate compilation policy that helped establish the prepared plan's assertions. + public let compilationOptions: JSONSchemaOptions + /// The total completed candidate validations, including preceding invalid verdicts. + public let attempts: UInt64 + /// The shared invocation ledger, including all completed candidate checks. + /// + /// evaluationWorkUnits is included in workUnits. Preparation is separate. + /// Discarded validation ownership is released without refunding work. Retained bytes + /// snapshot certification, including conservative staging reservations; ownership + /// transfers to the caller after that snapshot. Logical bytes are not heap size or RSS. + public let statistics: JSONOperationStatistics +} diff --git a/Sources/JSONSchemaExamples/SchemaExampleConstraints.swift b/Sources/JSONSchemaExamples/SchemaExampleConstraints.swift new file mode 100644 index 0000000..c432f48 --- /dev/null +++ b/Sources/JSONSchemaExamples/SchemaExampleConstraints.swift @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema + +struct SchemaExampleConstraint { + let node: JSONSchemaQualifiedNode + let view: JSONSchemaQualifiedNode.Declaration + func value(_ key: JSONSchemaPlan.Keyword, meter: inout JSONWorkMeter) throws -> JSON? { + guard view.vocabulary.applies(key) else { return nil } + return try view.raw.objectValue?.lookup(key.rawValue, meter: &meter) + } + func children(_ keyword: JSONSchemaPlan.Keyword, token: String? = nil, + meter: inout JSONWorkMeter) throws -> [JSONSchemaQualifiedNode] { + guard view.vocabulary.applies(keyword) else { return [] } + var result: [JSONSchemaQualifiedNode] = [] + for i in 0.. [SchemaExampleConstraint] { + var pending: [Step] = [], active: [Int] = [], seen: [Int] = [], result: [SchemaExampleConstraint] = [] + func push(_ step: Step, meter: inout JSONWorkMeter) throws { + try meter.require(.evaluationFrames, UInt64(pending.count) + 1) + try meter.retain(JSONLogicalWeights.frame); pending.append(step) + } + for node in nodes.reversed() { try push(.enter(node), meter: &meter) } + while let step = pending.popLast() { + try meter.work(); try meter.release(JSONLogicalWeights.frame) + switch step { + case .exit(let identity): + guard active.last == identity else { throw JSONFailure(.configuration, code: .invalidOperation) } + active.removeLast(); try meter.release(JSONLogicalWeights.indexSlot) + case .enter(let node): + for id in active { + try meter.work() + if id == node.identity { throw JSONSchemaExampleFailure.unsupportedFeature(.cyclicReference) } + } + var visited = false + for id in seen { try meter.work(); if id == node.identity { visited = true; break } } + if visited { continue } + try meter.charge(.schemaNodes); try meter.retain(JSONLogicalWeights.indexSlot * 2 + 64) + seen.append(node.identity); active.append(node.identity) + let view = try node.declaration(meter: &meter), constraint = SchemaExampleConstraint(node: node, view: view) + result.append(constraint) + try push(.exit(node.identity), meter: &meter) + // AllOf is conjunction; no alternative branch or conditional is flattened. + let children = try constraint.children(.allOf, meter: &meter) + for child in children.reversed() { try push(.enter(child), meter: &meter) } + for kind in SchemaExampleVersion.referencePushOrder { + for i in 0.. String { + // UInt64 decimal formatting is bounded by twenty ASCII digits. It must + // remain positional text (index 10 is "10", not normalized JSON "1e1"). + try meter.retain(40) + for _ in 0..<20 { try meter.work(subsystem: .numericWorkUnits) } + return String(value) + } + static func appendNodes(_ input: [JSONSchemaQualifiedNode], to output: inout [JSONSchemaQualifiedNode], + meter: inout JSONWorkMeter) throws { + for node in input { try meter.work(); try meter.retain(JSONLogicalWeights.indexSlot); output.append(node) } + } + static func propertyNodes(_ constraints: [SchemaExampleConstraint], key: JSONKey, + meter: inout JSONWorkMeter) throws -> [JSONSchemaQualifiedNode] { + var result: [JSONSchemaQualifiedNode] = [] + for c in constraints { + let declared = try c.value(.properties, meter: &meter)?.objectValue?.lookup(key.string, meter: &meter) + let nodes = try c.children(declared == nil ? .additionalProperties : .properties, + token: declared == nil ? nil : key.string, meter: &meter) + try appendNodes(nodes, to: &result, meter: &meter) + } + return result + } + static func textLess(_ a: String, _ b: String, meter: inout JSONWorkMeter) throws -> Bool { + var a = a.utf8.makeIterator(), b = b.utf8.makeIterator() + while true { + try meter.work(); let x = a.next(), y = b.next() + if x != y { if let x, let y { return x < y }; return x == nil } + if x == nil { return false } + } + } + static func insert(_ spelling: String, into keys: inout [JSONKey], meter: inout JSONWorkMeter) throws { + var position = keys.count + for (i, key) in keys.enumerated() { + if try JSONComparison.textEquals(key.string, spelling, meter: &meter) { return } + if try textLess(spelling, key.string, meter: &meter) { position = i; break } + } + try meter.require(.objectMembers, UInt64(keys.count) + 1) + try JSONAdmission.text(spelling, key: true, meter: &meter, owned: true) + let key = try JSONKey(spelling, meter: &meter) + try meter.retain(JSONLogicalWeights.indexSlot) + for _ in position.. [JSONKey]? { + guard let bounds = try SchemaExamplePrimitives.size(constraints, minimum: .minProperties, maximum: .maxProperties, + capacity: .objectMembers, meter: &meter) else { return nil } + var keys: [JSONKey] = [] + for c in constraints { + if let required = try c.value(.required, meter: &meter)?.arrayValue { + for value in required { + try meter.work() + guard let spelling = value.stringValue else { throw JSONFailure(.configuration, code: .invalidOperation) } + try insert(spelling, into: &keys, meter: &meter) + } + } + } + if UInt64(keys.count) > bounds.maximum { return nil } + var declared: [JSONKey] = [] + for c in constraints { + if let properties = try c.value(.properties, meter: &meter)?.objectValue { + for member in properties { + try meter.work() + if member.value.booleanValue != false { try insert(member.key.string, into: &declared, meter: &meter) } + } + } + } + for key in declared { + if UInt64(keys.count) >= bounds.minimum { break } + try insert(key.string, into: &keys, meter: &meter) + } + var ordinal: UInt64 = 0 + while UInt64(keys.count) < bounds.minimum { + try meter.work() + let suffix = try integerText(ordinal, meter: &meter) + try meter.retain(UInt64(suffix.utf8.count) + 1) + for _ in suffix.utf8 { try meter.work() } + try insert("p" + suffix, into: &keys, meter: &meter) + ordinal = try meter.adding(.workUnits, ordinal, 1) + } + return keys + } + static func typeMask(_ constraints: [SchemaExampleConstraint], meter: inout JSONWorkMeter) throws -> UInt8 { + var mask: UInt8 = 127 + for c in constraints { + if c.view.raw.booleanValue == false { return 0 } + guard let type = try c.value(.type, meter: &meter) else { continue } + let values: [JSON] + if type.stringValue != nil { try meter.retain(JSONLogicalWeights.arraySlot); values = [type] } + else if let array = type.arrayValue { values = array } + else { throw JSONFailure(.configuration, code: .invalidOperation) } + var accepted: UInt8 = 0 + for value in values { + try meter.work() + guard let name = value.stringValue else { throw JSONFailure(.configuration, code: .invalidOperation) } + // Only the compiler's seven ASCII type names can reach this mapping. + for (spelling, bits) in typeNames where try JSONComparison.textEquals(name, spelling, meter: &meter) { accepted |= bits } + } + mask &= accepted + } + return mask + } + static let typeNames: [(String, UInt8)] = [("null",1),("boolean",2),("integer",4),("number",12),("string",16),("array",32),("object",64)] + static let hints: [JSONSchemaPlan.Keyword] = [.type,.minimum,.maximum,.exclusiveMinimum,.exclusiveMaximum,.multipleOf,.minLength,.maxLength,.pattern,.format,.required,.properties,.minProperties,.maxProperties,.items,.prefixItems,.minItems,.maxItems,.contains] + static func containsMinimum(_ c: SchemaExampleConstraint, meter: inout JSONWorkMeter) throws -> UInt64? { + guard try c.value(.contains, meter: &meter) != nil else { return nil } + if let number = try c.value(.minContains, meter: &meter)?.numberValue { + return try number.integer(as: UInt64.self, meter: &meter) + } + return 1 + } + static func construct(nodes: [JSONSchemaQualifiedNode], variation: UInt64, + profile: JSONSchemaExampleProfile, corpus: [JSON], assertion: Bool, + random: inout SchemaExampleRandom, trace: inout SchemaExampleTrace, + meter: inout JSONWorkMeter) throws -> JSON? { + var frames: [SchemaExampleFrame] = [], completed: JSON? + func push(_ frame: SchemaExampleFrame, meter: inout JSONWorkMeter) throws { + try meter.require(.evaluationFrames, UInt64(frames.count) + 1) + try meter.retain(JSONLogicalWeights.frame); frames.append(frame) + } + try push(.value(nodes, 0, variation), meter: &meter) + while let frame = frames.popLast() { + try meter.work(); try meter.release(JSONLogicalWeights.frame) + switch frame { + case .value(let nodes, let depth, let ordinal): + try trace.event(6, depth, ordinal, meter: &meter) + let constraints = try SchemaExampleConjunction.gather(nodes, meter: &meter) + var declared: JSON?, hasHint = nodes.isEmpty + for c in constraints { + if c.view.raw.booleanValue == false { return nil } + if let value = try c.value(.const, meter: &meter) { declared = value; break } + if let values = try c.value(.enum, meter: &meter)?.arrayValue, !values.isEmpty { + declared = values[Int(ordinal % UInt64(values.count))]; break + } + for hint in hints { if try c.value(hint, meter: &meter) != nil { hasHint = true; break } } + if c.view.raw.booleanValue == true || c.view.raw.objectValue?.isEmpty == true { hasHint = true } + } + // Whole-instance const/enum candidates were already tried by discovery. + if depth == 0 && declared != nil { return nil } + if let declared { + try JSONAdmission.value(declared, meter: &meter, owned: true, depth: depth) + completed = declared; continue + } + guard hasHint else { return nil } + try meter.charge(.nodes); try meter.retain(JSONLogicalWeights.node) + let mask = try typeMask(constraints, meter: &meter) + var kinds: [UInt8] = [] + for kind: UInt8 in [1,2,4,16,32,64] { + if mask & kind != 0 { try meter.retain(1); kinds.append(kind) } + } + guard !kinds.isEmpty else { return nil } + let kind = kinds[Int(ordinal % UInt64(kinds.count))] + switch kind { + case 1: completed = .null + case 2: completed = .boolean(ordinal % 2 != 0) + case 4: + guard let number = try SchemaExamplePrimitives.number(constraints, integral: mask & 8 == 0, variation: ordinal, meter: &meter) else { return nil } + completed = .number(number) + case 16: + guard let text = try SchemaExamplePrimitives.string(constraints, variation: ordinal, profile: profile, corpus: corpus, + assertion: assertion, random: &random, trace: &trace, meter: &meter) else { return nil } + completed = .string(text) + case 32: + let container = try meter.adding(.maxDepth, depth, 1) + guard let bounds = try SchemaExamplePrimitives.size(constraints, minimum: .minItems, maximum: .maxItems, + capacity: .arrayItems, meter: &meter) else { return nil } + var unique = false, count = bounds.minimum + for c in constraints { + if try c.value(.uniqueItems, meter: &meter)?.booleanValue == true { unique = true } + if let minimum = try containsMinimum(c, meter: &meter) { + try meter.require(.arrayItems, minimum) + if let maximum = try c.value(.maxContains, meter: &meter)?.numberValue, + try maximum.integer(as: UInt64.self, meter: &meter) < minimum { return nil } + count = max(count, minimum) + } + } + guard count <= bounds.maximum else { return nil } + try push(.array(.init(constraints: constraints, count: count, depth: container, variation: ordinal, unique: unique)), meter: &meter) + default: + let container = try meter.adding(.maxDepth, depth, 1) + guard let keys = try objectKeys(constraints, meter: &meter) else { return nil } + try push(.object(.init(constraints: constraints, keys: keys, depth: container, variation: ordinal)), meter: &meter) + } + case .object(var state): + if state.awaiting { + guard let value = completed else { throw JSONFailure(.configuration, code: .invalidOperation) } + try meter.retain(JSONLogicalWeights.member) + state.members.append(.init(state.keys[state.position - 1], value: value)); completed = nil; state.awaiting = false + } + if state.position == state.keys.count { + completed = .object(try JSONObject.indexed(admitted: state.members, meter: &meter)); continue + } + let nodes = try propertyNodes(state.constraints, key: state.keys[state.position], meter: &meter) + state.position += 1; state.awaiting = true + let depth = state.depth, ordinal = state.variation + try push(.object(state), meter: &meter); try push(.value(nodes, depth, ordinal), meter: &meter) + case .array(var state): + if state.awaiting { + guard let value = completed else { throw JSONFailure(.configuration, code: .invalidOperation) } + try meter.retain(JSONLogicalWeights.arraySlot); state.values.append(value); completed = nil; state.awaiting = false + } + let index = UInt64(state.values.count) + if index == state.count { completed = .array(state.values); continue } + var nodes: [JSONSchemaQualifiedNode] = [] + let token = try integerText(index, meter: &meter) + for c in state.constraints { + let prefix = try c.value(.prefixItems, meter: &meter)?.arrayValue + let kind: JSONSchemaPlan.Keyword = index < UInt64(prefix?.count ?? 0) ? .prefixItems : .items + try appendNodes(c.children(kind, token: kind == .prefixItems ? token : nil, meter: &meter), to: &nodes, meter: &meter) + if let minimum = try containsMinimum(c, meter: &meter), index < minimum { + try appendNodes(c.children(.contains, meter: &meter), to: &nodes, meter: &meter) + } + } + let ordinal = try meter.adding(.workUnits, state.variation, state.unique ? index : 0) + let depth = state.depth; state.awaiting = true + try push(.array(state), meter: &meter); try push(.value(nodes, depth, ordinal), meter: &meter) + } + } + return completed + } +} diff --git a/Sources/JSONSchemaExamples/SchemaExampleDecimal.swift b/Sources/JSONSchemaExamples/SchemaExampleDecimal.swift new file mode 100644 index 0000000..299ad4a --- /dev/null +++ b/Sources/JSONSchemaExamples/SchemaExampleDecimal.swift @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON + +// Dense scratch is deliberately finite. Exponents stay sparse until an exact +// quotient needs alignment, which must fit numberDigits before zero expansion. +enum SchemaExampleDecimal { + struct Integer { let negative: Bool; let digits: [UInt8] } + static func tick(_ meter: inout JSONWorkMeter) throws { try meter.work(subsystem: .numericWorkUnits) } + static func append(_ digit: UInt8, to digits: inout [UInt8], meter: inout JSONWorkMeter) throws { + try tick(&meter); try meter.require(.numberDigits, UInt64(digits.count) + 1) + try meter.retain(1); digits.append(digit) + } + static func zero(_ digits: [UInt8]) -> Bool { digits.count == 1 && digits[0] == 0 } + static func trim(_ digits: [UInt8], meter: inout JSONWorkMeter) throws -> [UInt8] { + var start = 0 + while start < digits.count - 1 && digits[start] == 0 { try tick(&meter); start += 1 } + var result: [UInt8] = [] + for digit in digits[start...] { try append(digit, to: &result, meter: &meter) } + return result + } + static func compare(_ a: [UInt8], _ b: [UInt8], meter: inout JSONWorkMeter) throws -> Int { + try tick(&meter) + if a.count != b.count { return a.count < b.count ? -1 : 1 } + for i in a.indices { try tick(&meter); if a[i] != b[i] { return a[i] < b[i] ? -1 : 1 } } + return 0 + } + static func subtract(_ a: [UInt8], _ b: [UInt8], meter: inout JSONWorkMeter) throws -> [UInt8] { + var reversed: [UInt8] = [], borrow = 0 + for offset in 0.. [UInt8] { + var reversed: [UInt8] = [], carry = 0 + for offset in 0.. Integer { + var reversed: [UInt8] = [], remainder = amount + repeat { try append(UInt8(remainder % 10), to: &reversed, meter: &meter); remainder /= 10 } while remainder != 0 + var digits: [UInt8] = [] + for d in reversed.reversed() { try append(d, to: &digits, meter: &meter) } + if value.negative == negative { return .init(negative: negative, digits: try add(value.digits, digits, meter: &meter)) } + let order = try compare(value.digits, digits, meter: &meter) + if order >= 0 { return .init(negative: value.negative, digits: try subtract(value.digits, digits, meter: &meter)) } + return .init(negative: negative, digits: try subtract(digits, value.digits, meter: &meter)) + } + static func aligned(_ c: DecimalComponents, exponent: Int64, meter: inout JSONWorkMeter) throws -> [UInt8] { + if zero(c.digits) { try meter.retain(1); return [0] } + let (shift, overflow) = c.exponent.subtractingReportingOverflow(exponent) + guard !overflow, shift >= 0 else { throw JSONFailure(.resource, code: .counterOverflow) } + _ = try meter.adding(.numberDigits, UInt64(c.digits.count), UInt64(shift)) + var result: [UInt8] = [] + for d in c.digits { try append(d, to: &result, meter: &meter) } + for _ in 0.. (quotient: [UInt8], remainder: Bool) { + guard !zero(b) else { throw JSONFailure(.configuration, code: .invalidNumber) } + try meter.retain(1) + var remainder: [UInt8] = [0], quotient: [UInt8] = [] + for digit in a { + try tick(&meter) + if zero(remainder) { remainder[0] = digit } else { try append(digit, to: &remainder, meter: &meter) } + var q: UInt8 = 0 + while try compare(remainder, b, meter: &meter) >= 0 { + remainder = try subtract(remainder, b, meter: &meter); q += 1 + } + guard q <= 9 else { throw JSONFailure(.configuration, code: .invalidOperation) } + try append(q, to: "ient, meter: &meter) + } + return (try trim(quotient, meter: &meter), !zero(remainder)) + } + static func multiply(_ a: [UInt8], _ b: [UInt8], meter: inout JSONWorkMeter) throws -> [UInt8] { + let count = try meter.adding(.retainedBytes, UInt64(a.count), UInt64(b.count)) + try meter.retain(count) + for _ in 0.. JSONNumber { + var digits = try trim(digits, meter: &meter), exponent = exponent + while digits.count > 1 && digits.last == 0 { + try tick(&meter); digits.removeLast() + let (next, overflow) = exponent.addingReportingOverflow(1) + guard !overflow else { throw JSONFailure(.resource, code: .counterOverflow) }; exponent = next + } + try meter.require(.absoluteExponent, exponent.magnitude) + return JSONNumber(components: .init(negative: negative && !zero(digits), digits: digits, exponent: zero(digits) ? 0 : exponent)) + } + static func integralStep(_ step: JSONNumber, meter: inout JSONWorkMeter) throws -> JSONNumber { + let c = step.components + if c.exponent >= 0 { return step } + var digits = c.digits + for factor in [2, 5] { + for _ in 0.. JSONNumber { + let a = bound.components, b = step.components, common = min(a.exponent, b.exponent) + // Preserve sparse, already-aligned endpoints without materializing their quotient. + if variation == 0, !open, try bound.isMultiple(of: step, meter: &meter) { return bound } + let dividend = try aligned(a, exponent: common, meter: &meter) + let divisor = try aligned(b, exponent: common, meter: &meter) + let division = try divide(dividend, by: divisor, meter: &meter) + var integer = Integer(negative: a.negative, digits: division.quotient) + if division.remainder && up != a.negative { + integer = try offset(integer, by: 1, negative: a.negative, meter: &meter) + } + if !division.remainder && open { integer = try offset(integer, by: 1, negative: !up, meter: &meter) } + if variation > 0 { integer = try offset(integer, by: variation, negative: !up, meter: &meter) } + let product = try multiply(integer.digits, b.digits, meter: &meter) + return try number(product, negative: integer.negative, exponent: b.exponent, meter: &meter) + } +} diff --git a/Sources/JSONSchemaExamples/SchemaExampleDeterminism.swift b/Sources/JSONSchemaExamples/SchemaExampleDeterminism.swift new file mode 100644 index 0000000..2029f18 --- /dev/null +++ b/Sources/JSONSchemaExamples/SchemaExampleDeterminism.swift @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema + +enum SchemaExampleVersion { + static let generator = "s3-candidates-v4" + static let rng = "splitmix64-v1" + static let snapshot = "swift-json-schema-snapshot-v1" + static let corpus = "swift-json-example-corpus-v1" + static let policy = "swift-json-example-policy-v1" + static let trace = "swift-json-example-trace-v2" + static let referencePushOrder: [JSONSchemaPlan.Keyword] = [.ref, .dynamicRef] + static let keywords: [(String, JSONSchemaPlan.Keyword)] = [("const", .const), ("default", .default), ("enum", .enum), ("examples", .examples)] +} + +// No Sendable conformance: mutable state belongs to one synchronous invocation. +struct SchemaExampleRandom: ~Copyable { + private var state: UInt64 + private(set) var draws: UInt64 = 0 + init(seed: UInt64) { state = seed } + + mutating func next(meter: inout JSONWorkMeter) throws -> UInt64 { + for _ in 0..<10 { try meter.work() } + draws = try meter.adding(.workUnits, draws, 1) + state &+= 0x9E3779B97F4A7C15 + var z = state + z = (z ^ (z >> 30)) &* 0xBF58476D1CE4E5B9 + z = (z ^ (z >> 27)) &* 0x94D049BB133111EB + return z ^ (z >> 31) + } + + mutating func uniform(upperBound: UInt64, trace: inout SchemaExampleTrace, + meter: inout JSONWorkMeter) throws -> UInt64 { + try meter.checkpoint() + guard upperBound > 0 else { + let failure = JSONFailure(.configuration, code: .invalidOperation) + meter.recordFailure(failure); throw failure + } + for _ in 0..<3 { try meter.work() } + let threshold = (UInt64(0) &- upperBound) % upperBound + while true { + let candidate = try next(meter: &meter) + try trace.event(3, candidate, upperBound, meter: &meter) + for _ in 0..<2 { try meter.work() } + if candidate >= threshold { return candidate % upperBound } + } + } +} + +// A finite transport buffer; trace bytes share the native diagnostic byte quota. +struct SchemaExampleTransport: ~Copyable { + private(set) var bytes: [UInt8] = [] + let isTrace: Bool + init(header: String, isTrace: Bool = false, meter: inout JSONWorkMeter) throws { + self.isTrace = isTrace + for byte in header.utf8 { try append(byte, meter: &meter) } + } + mutating func append(_ byte: UInt8, meter: inout JSONWorkMeter) throws { + try meter.work() + try meter.require(.inputBytes, UInt64(bytes.count) + 1) + if isTrace { try meter.charge(.diagnosticBytes) } + try meter.retain(1); bytes.append(byte) + } + mutating func integer(_ value: UInt64, meter: inout JSONWorkMeter) throws { + for shift in stride(from: 56, through: 0, by: -8) { + try append(UInt8(truncatingIfNeeded: value >> shift), meter: &meter) + } + } + mutating func field(_ value: C, meter: inout JSONWorkMeter) throws where C.Element == UInt8 { + var length: UInt64 = 0 + for _ in value { try meter.work(); length = try meter.adding(.inputBytes, length, 1) } + try integer(length, meter: &meter) + for byte in value { try append(byte, meter: &meter) } + } + mutating func digest(meter: inout JSONWorkMeter) throws -> String { + let hash = try JSONSHA256Core.hexadecimal(bytes, accounting: &meter) + try meter.release(UInt64(bytes.count)) + bytes = [] + return hash + } +} + +struct SchemaExampleTrace: ~Copyable { + private var transport: SchemaExampleTransport + private(set) var events: UInt64 = 0 + private var finished = false + var retainedByteCount: UInt64 { UInt64(transport.bytes.count) } + init(snapshot: String, corpus: String, policy: String, seed: UInt64, + profile: JSONSchemaExampleProfile, meter: inout JSONWorkMeter) throws { + var transport = try SchemaExampleTransport(header: "SwiftJSON-example-trace-v2\n", isTrace: true, meter: &meter) + try meter.retain(JSONLogicalWeights.arraySlot * 4) + for text in [SchemaExampleVersion.generator, snapshot, corpus, policy] { + try transport.field(text.utf8, meter: &meter) + } + try meter.release(JSONLogicalWeights.arraySlot * 4) + try transport.integer(seed, meter: &meter) + try transport.append(profile == .minimal ? 0 : 1, meter: &meter) + self.transport = consume transport + } + mutating func event(_ code: UInt8, _ a: UInt64, _ b: UInt64, meter: inout JSONWorkMeter) throws { + guard !finished else { + let failure = JSONFailure(.configuration, code: .invalidOperation) + meter.recordFailure(failure); throw failure + } + try transport.append(code, meter: &meter) + try transport.integer(a, meter: &meter); try transport.integer(b, meter: &meter) + events = try meter.adding(.workUnits, events, 1) + } + mutating func digest(meter: inout JSONWorkMeter) throws -> String { + guard !finished else { + let failure = JSONFailure(.configuration, code: .invalidOperation) + meter.recordFailure(failure); throw failure + } + finished = true + // The transport owns exactly its bytes. There is no array-slot + // reservation here to refund; SHA-256 retains its 64-byte hex result. + return try transport.digest(meter: &meter) + } +} + +enum SchemaExampleExact { + static func bytes(_ value: JSON, meter: inout JSONWorkMeter) throws -> [UInt8] { + try meter.resetValueBounds() + return try JSONWriter(options: .init(keyOrder: .scalar, numberStyle: .normalized)).write(value, meter: &meter) + } + static func construct(_ value: JSON, meter: inout JSONWorkMeter) throws -> JSON { + let encoded = try bytes(value, meter: &meter) + try meter.resetValueBounds() + let normalized = try JSONParser().parse(encoded, meter: &meter) + try meter.release(UInt64(encoded.count)) + return normalized + } +} + +enum SchemaExampleDigests { + static func snapshot(document: JSONSchemaDocument, registry: JSONSchemaRegistry, + meter: inout JSONWorkMeter) throws -> String { + var transport = try SchemaExampleTransport(header: "SwiftJSON-schema-snapshot-v1\n", meter: &meter) + let count = try meter.adding(.schemaDocuments, UInt64(registry.documents.count), 1) + try transport.integer(count, meter: &meter) + for index in 0.. String { + var transport = try SchemaExampleTransport(header: "SwiftJSON-example-corpus-v1\n", meter: &meter) + let encoded = try SchemaExampleExact.bytes(.array(values), meter: &meter) + try transport.field(encoded, meter: &meter); try meter.release(UInt64(encoded.count)) + return try transport.digest(meter: &meter) + } + static func policy(_ options: JSONSchemaExampleOptions, meter: inout JSONWorkMeter) throws -> String { + var transport = try SchemaExampleTransport(header: "SwiftJSON-example-policy-v1\n", meter: &meter) + try transport.integer(options.maximumAttempts, meter: &meter) + try meter.retain(JSONLogicalWeights.arraySlot * 3) + for limits in [options.limits, options.compilationOptions.limits, options.validationOptions.limits] { + try transport.integer(UInt64(JSONResourceMetric.allCases.count), meter: &meter) + for metric in JSONResourceMetric.allCases { + try transport.field(metric.rawValue.utf8, meter: &meter) + try transport.integer(limits[metric], meter: &meter) + } + } + try meter.release(JSONLogicalWeights.arraySlot * 3) + try meter.retain(JSONLogicalWeights.arraySlot * 2) + for policy in [options.compilationOptions, options.validationOptions] { + try transport.append(policy.formatAssertion ? 1 : 0, meter: &meter) + try transport.append(policy.output == .flag ? 0 : 1, meter: &meter) + try transport.append(policy.diagnosticPolicy == .complete ? 0 : 1, meter: &meter) + try transport.append(policy.collectAnnotations ? 1 : 0, meter: &meter) + } + try meter.release(JSONLogicalWeights.arraySlot * 2) + return try transport.digest(meter: &meter) + } +} + +// Swift infers Sendable for internal value types with Sendable storage. Explicit +// unavailable conformances opt invocation-only mutable helpers out, with compiler +// rejection fixtures; immutable preparation remains checked Sendable. +@available(*, unavailable) +extension SchemaExampleRandom: Sendable {} +@available(*, unavailable) +extension SchemaExampleTrace: Sendable {} +@available(*, unavailable) +extension SchemaExampleTransport: Sendable {} diff --git a/Sources/JSONSchemaExamples/SchemaExamplePattern.swift b/Sources/JSONSchemaExamples/SchemaExamplePattern.swift new file mode 100644 index 0000000..2970c99 --- /dev/null +++ b/Sources/JSONSchemaExamples/SchemaExamplePattern.swift @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON + +// Proposal scanner only, never a regex matcher or schema-validity authority. +// Fully anchored sequences of printable literals, escaped punctuation or single +// ASCII ranges. Each atom may carry one exact {n}; other quantifiers are unsupported. +enum SchemaExamplePattern { + static let punctuation: [UInt8] = [92,94,36,46,124,63,42,43,40,41,91,93,123,125,47,45] + static func propose(_ pattern: String, variation: UInt64, minimum: UInt64, maximum: UInt64, + meter: inout JSONWorkMeter) throws -> String? { + var source: [UInt8] = [] + for byte in pattern.utf8 { + try meter.work(); try meter.require(.regexPatternBytes, UInt64(source.count) + 1) + try meter.retain(1); source.append(byte) + } + guard source.count >= 2, source.first == 94, source.last == 36 else { return nil } + let end = source.count - 1 + var position = 1, output: [UInt8] = [] + while position < end { + try meter.work() + let first = source[position], byte: UInt8 + if first == 92 { + guard position + 1 < end else { return nil } + let escaped = source[position + 1] + var accepted = false + for symbol in punctuation { try meter.work(); if symbol == escaped { accepted = true; break } } + guard accepted else { return nil } + byte = escaped; position += 2 + } else if first == 91 { + guard end - position >= 5, source[position + 2] == 45, source[position + 4] == 93 else { return nil } + let lower = source[position + 1], upper = source[position + 3] + guard lower >= 32, upper <= 126, lower <= upper, + lower != 92, lower != 94, lower != 93, upper != 92, upper != 93 else { return nil } + byte = lower + UInt8(variation % UInt64(upper - lower + 1)); position += 5 + } else { + guard first >= 32, first <= 126 else { return nil } + var special = false + for symbol in punctuation { try meter.work(); if symbol == first && symbol != 47 && symbol != 45 { special = true; break } } + guard !special else { return nil } + byte = first; position += 1 + } + var repetitions: UInt64 = 1 + if position < end && source[position] == 123 { + position += 1; repetitions = 0 + let start = position + while position < end && source[position] >= 48 && source[position] <= 57 { + try meter.work(subsystem: .numericWorkUnits) + let (product, badProduct) = repetitions.multipliedReportingOverflow(by: 10) + let (next, badSum) = product.addingReportingOverflow(UInt64(source[position] - 48)) + guard !badProduct, !badSum else { + let failure = JSONFailure(.resource, code: .counterOverflow) + meter.recordFailure(failure); throw failure + } + try meter.require(.stringBytes, next) + repetitions = next; position += 1 + } + guard position > start, position < end, source[position] == 125 else { return nil } + position += 1 + } + let length = try meter.adding(.stringBytes, UInt64(output.count), repetitions) + guard length <= maximum else { return nil } + for _ in 0..= minimum else { return nil } + for _ in output { try meter.work(); try meter.retain(1) } + return String(decoding: output, as: UTF8.self) + } +} diff --git a/Sources/JSONSchemaExamples/SchemaExamplePrimitives.swift b/Sources/JSONSchemaExamples/SchemaExamplePrimitives.swift new file mode 100644 index 0000000..46d3bad --- /dev/null +++ b/Sources/JSONSchemaExamples/SchemaExamplePrimitives.swift @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema + +// Conservative hints only. Complete native validation, not these proposals, +// establishes whether all constraints on the resulting instance are satisfied. +enum SchemaExamplePrimitives { + struct Bound { let number: JSONNumber; let open: Bool } + static func number(_ constraints: [SchemaExampleConstraint], integral: Bool, variation: UInt64, + meter: inout JSONWorkMeter) throws -> JSONNumber? { + try meter.retain(64) + var lower: Bound?, upper: Bound?, step: JSONNumber?, precision: Int64 = 0 + for c in constraints { + for kind in [JSONSchemaPlan.Keyword.minimum, .exclusiveMinimum, .maximum, .exclusiveMaximum] { + guard let n = try c.value(kind, meter: &meter)?.numberValue else { continue } + precision = min(precision, n.components.exponent) + let candidate = Bound(number: n, open: kind == .exclusiveMinimum || kind == .exclusiveMaximum) + if kind == .minimum || kind == .exclusiveMinimum { + if let prior = lower { + let order = try n.compare(to: prior.number, meter: &meter) + if order == .orderedDescending || order == .orderedSame && candidate.open { lower = candidate } + } else { lower = candidate } + } else { + if let prior = upper { + let order = try n.compare(to: prior.number, meter: &meter) + if order == .orderedAscending || order == .orderedSame && candidate.open { upper = candidate } + } else { upper = candidate } + } + } + if step == nil { step = try c.value(.multipleOf, meter: &meter)?.numberValue } + } + if let lower, let upper { + let order = try lower.number.compare(to: upper.number, meter: &meter) + if order == .orderedDescending || order == .orderedSame && (lower.open || upper.open) { return nil } + } + let stride: JSONNumber + if let step { + stride = integral ? try SchemaExampleDecimal.integralStep(step, meter: &meter) : step + } else { + let exponent = integral ? 0 : precision - 1 // admitted exponent magnitudes fit Int capacity, safely below Int64.max + try meter.require(.absoluteExponent, exponent.magnitude) + try meter.retain(1) + stride = JSONNumber(components: .init(negative: false, digits: [1], exponent: exponent)) + } + func fits(_ n: JSONNumber, meter: inout JSONWorkMeter) throws -> Bool { + if let lower { + let order = try n.compare(to: lower.number, meter: &meter) + if order == .orderedAscending || order == .orderedSame && lower.open { return false } + } + if let upper { + let order = try n.compare(to: upper.number, meter: &meter) + if order == .orderedDescending || order == .orderedSame && upper.open { return false } + } + return true + } + let zero = JSONNumber(integer: 0) + let candidate: JSONNumber + if try fits(zero, meter: &meter) { + candidate = try SchemaExampleDecimal.rounded(zero, step: stride, up: true, open: false, variation: variation, meter: &meter) + } else if let lower { + candidate = try SchemaExampleDecimal.rounded(lower.number, step: stride, up: true, open: lower.open, variation: variation, meter: &meter) + } else if let upper { + candidate = try SchemaExampleDecimal.rounded(upper.number, step: stride, up: false, open: upper.open, variation: variation, meter: &meter) + } else { return nil } + return try fits(candidate, meter: &meter) ? candidate : nil + } + + static func size(_ constraints: [SchemaExampleConstraint], minimum: JSONSchemaPlan.Keyword, + maximum: JSONSchemaPlan.Keyword, capacity: JSONResourceMetric, + meter: inout JSONWorkMeter) throws -> (minimum: UInt64, maximum: UInt64)? { + var lo: UInt64 = 0, hi = meter.limits[capacity] + for c in constraints { + if let n = try c.value(minimum, meter: &meter)?.numberValue { lo = max(lo, try n.integer(as: UInt64.self, meter: &meter)) } + if let n = try c.value(maximum, meter: &meter)?.numberValue { hi = min(hi, try n.integer(as: UInt64.self, meter: &meter)) } + } + try meter.require(capacity, lo) + return lo <= hi ? (lo,hi) : nil + } + static func string(_ constraints: [SchemaExampleConstraint], variation: UInt64, + profile: JSONSchemaExampleProfile, corpus: [JSON], assertion: Bool, + random: inout SchemaExampleRandom, trace: inout SchemaExampleTrace, + meter: inout JSONWorkMeter) throws -> String? { + guard let bounds = try size(constraints, minimum: .minLength, maximum: .maxLength, + capacity: .stringBytes, meter: &meter) else { return nil } + for c in constraints { + if let pattern = try c.value(.pattern, meter: &meter)?.stringValue, + let proposal = try SchemaExamplePattern.propose(pattern, variation: variation, minimum: bounds.minimum, + maximum: bounds.maximum, meter: &meter) { return proposal } + } + var email = false + for c in constraints { + if c.view.formatAssertion || assertion, + let format = try c.value(.format, meter: &meter)?.stringValue, + try JSONComparison.textEquals(format, "email", meter: &meter) { email = true } + } + var token = "" + if email { token = "example@example.com" } + else if profile == .demonstration && variation == 0 { + var strings: [String] = [] + for value in corpus { + try meter.work() + if let text = value.stringValue { try meter.retain(JSONLogicalWeights.indexSlot); strings.append(text) } + } + if !strings.isEmpty { + token = strings[Int(try random.uniform(upperBound: UInt64(strings.count), trace: &trace, meter: &meter))] + } + } + var bytes: [UInt8] = [], scalars: UInt64 = 0 + func append(_ byte: UInt8, meter: inout JSONWorkMeter) throws { + try meter.work(); try meter.require(.stringBytes, UInt64(bytes.count) + 1) + try meter.charge(.aggregateTextBytes); try meter.retain(1); bytes.append(byte) + } + for scalar in token.unicodeScalars { + try meter.work() + if scalars == bounds.maximum { break } + // A Unicode scalar has at most four bytes. Charge the bounded temporary + // before native scalar encoding, then the actual output bytes. + try meter.retain(4) + for byte in String(scalar).utf8 { try append(byte, meter: &meter) } + try meter.release(4); scalars += 1 + } + var length = bounds.minimum + if variation > 0 && length == 0 { length = min(1, bounds.maximum) } + while scalars < length { + try append(UInt8(97 + variation % 26), meter: &meter); scalars += 1 + } + // String's owned UTF-8 result overlaps its construction buffer. + for _ in bytes { try meter.work(); try meter.retain(1) } + return String(decoding: bytes, as: UTF8.self) + } +} diff --git a/Tests/Fixtures/SchemaExamples/Manifest.json b/Tests/Fixtures/SchemaExamples/Manifest.json new file mode 100644 index 0000000..c02d1d7 --- /dev/null +++ b/Tests/Fixtures/SchemaExamples/Manifest.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "packet_sha256": "f67ec2f5d0bfd223f54a3577f93c03951a7992ecab524910ba44f6bc8621c4a1", + "original_sha256": "6f2bfd4fdb7c7e206b6570c2156ca8e248831840733b0258aa8cd5938b35649f", + "amended_sha256": "5b3e6e40e83439a810d0eb7ac412ce158a4e582f36b9d36d6e313c159654ddd3", + "amendment": "G0: 9007199254740994.0 is mathematically integral and valid; add 9007199254740994.5 as negative. Original retained unchanged.", + "g3_must_generate_ids": [ + "boolean-true", + "null-kind", + "string-length", + "exact-integer-beyond-2pow53", + "exact-decimal-multiple-of", + "numeric-exclusive-near-zero", + "object-required-and-null", + "unicode-exact-scalar-keys", + "undeclared-required-key", + "object-min-max-properties", + "array-prefix-items", + "array-unique", + "enum", + "const-exact-decimal", + "anyof", + "allof-object-composition", + "local-ref", + "format-annotation", + "format-assertion", + "invalid-default-annotation", + "invalid-examples-annotation", + "false-nested-schema", + "object-number-key-strings", + "required-with-additional-properties-false" + ], + "g3_sound_proof_ids": [ + "boolean-false", + "not-true" + ], + "other_gate_cases": 18, + "case_count": 44, + "amended_witnesses": 155, + "g4_must_generate_ids": [ + "array-contains", + "condition-then-else", + "dependent-required", + "dependent-schema", + "pattern-properties", + "property-names", + "unevaluated-properties-composition", + "simple-regex", + "mincontains-with-prefixitems", + "array-mixed-evaluation" + ], + "g4_may_fail_ids": [ + "oneof-overlap", + "recursive-tree", + "dynamic-ref", + "conflicting-allof", + "unfulfillable-unique-enum", + "external-offline-ref", + "not-string", + "oneof-overlap-limited" + ] +} diff --git a/Tests/Fixtures/SchemaExamples/S3-G7-seeded-v1.json b/Tests/Fixtures/SchemaExamples/S3-G7-seeded-v1.json new file mode 100644 index 0000000..b831095 --- /dev/null +++ b/Tests/Fixtures/SchemaExamples/S3-G7-seeded-v1.json @@ -0,0 +1,934 @@ +{ + "version": 1, + "recipe": "eight bounded grammar families x eight exact variants; deterministic seed set 0,1,42,UInt64.max", + "cases": [ + { + "id": "integer-0", + "schema": { + "type": "integer", + "minimum": 9007199254740993, + "maximum": 9007199254740996 + }, + "invalid": 9007199254740992 + }, + { + "id": "scalar-0", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 0 + }, + "é": { + "const": 9007199254740993 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 0 + } + }, + { + "id": "tuple-0", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 0 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 0 + ] + }, + { + "id": "conjunction-0", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + }, + { + "maximum": 5 + } + ] + }, + "invalid": 6 + }, + { + "id": "choice-0", + "schema": { + "oneOf": [ + { + "const": 0 + }, + { + "const": "s0" + } + ] + }, + "invalid": null + }, + { + "id": "reference-0", + "schema": { + "$defs": { + "v": { + "const": 9007199254740993 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740992 + }, + { + "id": "string-0", + "schema": { + "type": "string", + "minLength": 0, + "maxLength": 1 + }, + "invalid": "xx" + }, + { + "id": "hints-0", + "schema": { + "type": "string", + "default": 0, + "examples": [ + "v0" + ], + "enum": [ + "v0", + "z0" + ] + }, + "invalid": 0 + }, + { + "id": "integer-1", + "schema": { + "type": "integer", + "minimum": 9007199254740994, + "maximum": 9007199254740997 + }, + "invalid": 9007199254740993 + }, + { + "id": "scalar-1", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 1 + }, + "é": { + "const": 9007199254740994 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 1 + } + }, + { + "id": "tuple-1", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 1 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 1 + ] + }, + { + "id": "conjunction-1", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + }, + { + "maximum": 6 + } + ] + }, + "invalid": 7 + }, + { + "id": "choice-1", + "schema": { + "oneOf": [ + { + "const": 1 + }, + { + "const": "s1" + } + ] + }, + "invalid": null + }, + { + "id": "reference-1", + "schema": { + "$defs": { + "v": { + "const": 9007199254740994 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740993 + }, + { + "id": "string-1", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 2 + }, + "invalid": "xxx" + }, + { + "id": "hints-1", + "schema": { + "type": "string", + "default": 1, + "examples": [ + "v1" + ], + "enum": [ + "v1", + "z1" + ] + }, + "invalid": 1 + }, + { + "id": "integer-2", + "schema": { + "type": "integer", + "minimum": 9007199254740995, + "maximum": 9007199254740998 + }, + "invalid": 9007199254740994 + }, + { + "id": "scalar-2", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 2 + }, + "é": { + "const": 9007199254740995 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 2 + } + }, + { + "id": "tuple-2", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 2 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 2 + ] + }, + { + "id": "conjunction-2", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 2 + }, + { + "maximum": 7 + } + ] + }, + "invalid": 8 + }, + { + "id": "choice-2", + "schema": { + "oneOf": [ + { + "const": 2 + }, + { + "const": "s2" + } + ] + }, + "invalid": null + }, + { + "id": "reference-2", + "schema": { + "$defs": { + "v": { + "const": 9007199254740995 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740994 + }, + { + "id": "string-2", + "schema": { + "type": "string", + "minLength": 2, + "maxLength": 3 + }, + "invalid": "xxxx" + }, + { + "id": "hints-2", + "schema": { + "type": "string", + "default": 2, + "examples": [ + "v2" + ], + "enum": [ + "v2", + "z2" + ] + }, + "invalid": 2 + }, + { + "id": "integer-3", + "schema": { + "type": "integer", + "minimum": 9007199254740996, + "maximum": 9007199254740999 + }, + "invalid": 9007199254740995 + }, + { + "id": "scalar-3", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 3 + }, + "é": { + "const": 9007199254740996 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 3 + } + }, + { + "id": "tuple-3", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 3 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 3 + ] + }, + { + "id": "conjunction-3", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 3 + }, + { + "maximum": 8 + } + ] + }, + "invalid": 9 + }, + { + "id": "choice-3", + "schema": { + "oneOf": [ + { + "const": 3 + }, + { + "const": "s3" + } + ] + }, + "invalid": null + }, + { + "id": "reference-3", + "schema": { + "$defs": { + "v": { + "const": 9007199254740996 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740995 + }, + { + "id": "string-3", + "schema": { + "type": "string", + "minLength": 3, + "maxLength": 4 + }, + "invalid": "xxxxx" + }, + { + "id": "hints-3", + "schema": { + "type": "string", + "default": 3, + "examples": [ + "v3" + ], + "enum": [ + "v3", + "z3" + ] + }, + "invalid": 3 + }, + { + "id": "integer-4", + "schema": { + "type": "integer", + "minimum": 9007199254740997, + "maximum": 9007199254741000 + }, + "invalid": 9007199254740996 + }, + { + "id": "scalar-4", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 4 + }, + "é": { + "const": 9007199254740997 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 4 + } + }, + { + "id": "tuple-4", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 4 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 4 + ] + }, + { + "id": "conjunction-4", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 4 + }, + { + "maximum": 9 + } + ] + }, + "invalid": 10 + }, + { + "id": "choice-4", + "schema": { + "oneOf": [ + { + "const": 4 + }, + { + "const": "s4" + } + ] + }, + "invalid": null + }, + { + "id": "reference-4", + "schema": { + "$defs": { + "v": { + "const": 9007199254740997 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740996 + }, + { + "id": "string-4", + "schema": { + "type": "string", + "minLength": 4, + "maxLength": 5 + }, + "invalid": "xxxxxx" + }, + { + "id": "hints-4", + "schema": { + "type": "string", + "default": 4, + "examples": [ + "v4" + ], + "enum": [ + "v4", + "z4" + ] + }, + "invalid": 4 + }, + { + "id": "integer-5", + "schema": { + "type": "integer", + "minimum": 9007199254740998, + "maximum": 9007199254741001 + }, + "invalid": 9007199254740997 + }, + { + "id": "scalar-5", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 5 + }, + "é": { + "const": 9007199254740998 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 5 + } + }, + { + "id": "tuple-5", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 5 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 5 + ] + }, + { + "id": "conjunction-5", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 5 + }, + { + "maximum": 10 + } + ] + }, + "invalid": 11 + }, + { + "id": "choice-5", + "schema": { + "oneOf": [ + { + "const": 5 + }, + { + "const": "s5" + } + ] + }, + "invalid": null + }, + { + "id": "reference-5", + "schema": { + "$defs": { + "v": { + "const": 9007199254740998 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740997 + }, + { + "id": "string-5", + "schema": { + "type": "string", + "minLength": 5, + "maxLength": 6 + }, + "invalid": "xxxxxxx" + }, + { + "id": "hints-5", + "schema": { + "type": "string", + "default": 5, + "examples": [ + "v5" + ], + "enum": [ + "v5", + "z5" + ] + }, + "invalid": 5 + }, + { + "id": "integer-6", + "schema": { + "type": "integer", + "minimum": 9007199254740999, + "maximum": 9007199254741002 + }, + "invalid": 9007199254740998 + }, + { + "id": "scalar-6", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 6 + }, + "é": { + "const": 9007199254740999 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 6 + } + }, + { + "id": "tuple-6", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 6 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 6 + ] + }, + { + "id": "conjunction-6", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 6 + }, + { + "maximum": 11 + } + ] + }, + "invalid": 12 + }, + { + "id": "choice-6", + "schema": { + "oneOf": [ + { + "const": 6 + }, + { + "const": "s6" + } + ] + }, + "invalid": null + }, + { + "id": "reference-6", + "schema": { + "$defs": { + "v": { + "const": 9007199254740999 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740998 + }, + { + "id": "string-6", + "schema": { + "type": "string", + "minLength": 6, + "maxLength": 7 + }, + "invalid": "xxxxxxxx" + }, + { + "id": "hints-6", + "schema": { + "type": "string", + "default": 6, + "examples": [ + "v6" + ], + "enum": [ + "v6", + "z6" + ] + }, + "invalid": 6 + }, + { + "id": "integer-7", + "schema": { + "type": "integer", + "minimum": 9007199254741000, + "maximum": 9007199254741003 + }, + "invalid": 9007199254740999 + }, + { + "id": "scalar-7", + "schema": { + "type": "object", + "properties": { + "é": { + "const": 7 + }, + "é": { + "const": 9007199254741000 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "invalid": { + "é": 7 + } + }, + { + "id": "tuple-7", + "schema": { + "type": "array", + "prefixItems": [ + { + "const": 7 + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "items": false + }, + "invalid": [ + 7 + ] + }, + { + "id": "conjunction-7", + "schema": { + "type": "integer", + "allOf": [ + { + "minimum": 7 + }, + { + "maximum": 12 + } + ] + }, + "invalid": 13 + }, + { + "id": "choice-7", + "schema": { + "oneOf": [ + { + "const": 7 + }, + { + "const": "s7" + } + ] + }, + "invalid": null + }, + { + "id": "reference-7", + "schema": { + "$defs": { + "v": { + "const": 9007199254741000 + } + }, + "$ref": "#/$defs/v" + }, + "invalid": 9007199254740999 + }, + { + "id": "string-7", + "schema": { + "type": "string", + "minLength": 7, + "maxLength": 8 + }, + "invalid": "xxxxxxxxx" + }, + { + "id": "hints-7", + "schema": { + "type": "string", + "default": 7, + "examples": [ + "v7" + ], + "enum": [ + "v7", + "z7" + ] + }, + "invalid": 7 + } + ] +} diff --git a/Tests/Fixtures/SchemaExamples/S3-integral-correction-v1.json b/Tests/Fixtures/SchemaExamples/S3-integral-correction-v1.json new file mode 100644 index 0000000..1aa8f2b --- /dev/null +++ b/Tests/Fixtures/SchemaExamples/S3-integral-correction-v1.json @@ -0,0 +1,2152 @@ +{ + "fixtureFormat": "SwiftJSON.SchemaExamples.Cases/1", + "draft": "https://json-schema.org/draft/2020-12/schema", + "intendedFor": "Independent correctness oracle and Codex acceptance gates; not production generator outputs", + "cases": [ + { + "id": "boolean-true", + "generatorExpectation": "must-generate", + "featureTags": [ + "boolean" + ], + "schema": true, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": null, + "valid": true + }, + { + "label": "valid_2", + "instance": {}, + "valid": true + }, + { + "label": "valid_3", + "instance": [], + "valid": true + }, + { + "label": "valid_4", + "instance": 42, + "valid": true + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "boolean-false", + "generatorExpectation": "must-prove-unsatisfiable", + "featureTags": [ + "boolean", + "unsatisfiable" + ], + "schema": false, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": null, + "valid": false + }, + { + "label": "invalid_2", + "instance": {}, + "valid": false + }, + { + "label": "invalid_3", + "instance": 12, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "not-true", + "generatorExpectation": "must-prove-unsatisfiable", + "featureTags": [ + "not", + "unsatisfiable" + ], + "schema": { + "not": {} + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": null, + "valid": false + }, + { + "label": "invalid_2", + "instance": {}, + "valid": false + }, + { + "label": "invalid_3", + "instance": "x", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "null-kind", + "generatorExpectation": "must-generate", + "featureTags": [ + "type" + ], + "schema": { + "type": "null" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": null, + "valid": true + }, + { + "label": "invalid_1", + "instance": false, + "valid": false + }, + { + "label": "invalid_2", + "instance": 0, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "string-length", + "generatorExpectation": "must-generate", + "featureTags": [ + "string", + "unicode", + "minLength", + "maxLength" + ], + "schema": { + "type": "string", + "minLength": 2, + "maxLength": 4 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ab", + "valid": true + }, + { + "label": "valid_2", + "instance": "abc", + "valid": true + }, + { + "label": "valid_3", + "instance": "é", + "valid": true + }, + { + "label": "invalid_1", + "instance": "", + "valid": false + }, + { + "label": "invalid_2", + "instance": "é", + "valid": false + }, + { + "label": "invalid_3", + "instance": "abcde", + "valid": false + }, + { + "label": "invalid_4", + "instance": 7, + "valid": false + } + ], + "notes": "Length is Unicode scalars, not grapheme clusters", + "acceptanceGate": "G3" + }, + { + "id": "exact-integer-beyond-2pow53", + "generatorExpectation": "must-generate", + "featureTags": [ + "integer", + "exactNumber" + ], + "schema": { + "type": "integer", + "minimum": 9007199254740993, + "maximum": 9007199254740995 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 9007199254740993, + "valid": true + }, + { + "label": "valid_2", + "instance": 9007199254740994, + "valid": true + }, + { + "label": "invalid_1", + "instance": 9007199254740992, + "valid": false + }, + { + "label": "invalid_2", + "instance": 9007199254740996, + "valid": false + }, + { + "label": "valid_integral_decimal_lexeme", + "instance": 9007199254740994.0, + "valid": true + }, + { + "label": "invalid_fractional_number", + "instance": 9007199254740994.5, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "exact-decimal-multiple-of", + "generatorExpectation": "must-generate", + "featureTags": [ + "exactNumber", + "multipleOf", + "exclusiveMaximum" + ], + "schema": { + "type": "number", + "multipleOf": 0.01, + "minimum": 0.02, + "exclusiveMaximum": 0.05 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.02, + "valid": true + }, + { + "label": "valid_2", + "instance": 0.03, + "valid": true + }, + { + "label": "valid_3", + "instance": 0.04, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0.05, + "valid": false + }, + { + "label": "invalid_2", + "instance": 0.025, + "valid": false + }, + { + "label": "invalid_3", + "instance": 0.01, + "valid": false + } + ], + "notes": "Parse JSON numeric fractions as Decimal for independent Python oracle", + "acceptanceGate": "G3" + }, + { + "id": "numeric-exclusive-near-zero", + "generatorExpectation": "must-generate", + "featureTags": [ + "exactNumber", + "multipleOf", + "exclusiveMinimum", + "exclusiveMaximum" + ], + "schema": { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 0.001, + "multipleOf": 0.0001 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.0001, + "valid": true + }, + { + "label": "valid_2", + "instance": 0.0005, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0, + "valid": false + }, + { + "label": "invalid_2", + "instance": 0.001, + "valid": false + }, + { + "label": "invalid_3", + "instance": 0.00015, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "object-required-and-null", + "generatorExpectation": "must-generate", + "featureTags": [ + "object", + "required", + "null", + "additionalProperties" + ], + "schema": { + "type": "object", + "properties": { + "x": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "x" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "x": null + }, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "x": "abc" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": {}, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "x": 1 + }, + "valid": false + }, + { + "label": "invalid_3", + "instance": { + "x": "a", + "y": 0 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "unicode-exact-scalar-keys", + "generatorExpectation": "must-generate", + "featureTags": [ + "unicode", + "object", + "required", + "exactKeys" + ], + "schema": { + "type": "object", + "properties": { + "é": { + "const": 1 + }, + "é": { + "const": 2 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "é": 1, + "é": 2 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "é": 1 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "é": 2 + }, + "valid": false + }, + { + "label": "invalid_3", + "instance": { + "é": 2, + "é": 1 + }, + "valid": false + } + ], + "notes": "Two different decoded scalar sequences, even when canonically equivalent", + "acceptanceGate": "G3" + }, + { + "id": "undeclared-required-key", + "generatorExpectation": "must-generate", + "featureTags": [ + "required", + "additionalProperties" + ], + "schema": { + "type": "object", + "required": [ + "id" + ], + "additionalProperties": { + "type": "integer" + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "id": 0 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": {}, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "id": null + }, + "valid": false + } + ], + "notes": "Required key need not have a properties declaration", + "acceptanceGate": "G3" + }, + { + "id": "object-min-max-properties", + "generatorExpectation": "must-generate", + "featureTags": [ + "minProperties", + "maxProperties", + "additionalProperties" + ], + "schema": { + "type": "object", + "minProperties": 2, + "maxProperties": 2, + "additionalProperties": { + "type": "integer" + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "a": 1, + "b": 2 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": {}, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "a": 1 + }, + "valid": false + }, + { + "label": "invalid_3", + "instance": { + "a": 1, + "b": 2, + "c": 3 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-prefix-items", + "generatorExpectation": "must-generate", + "featureTags": [ + "prefixItems", + "items", + "minItems" + ], + "schema": { + "type": "array", + "prefixItems": [ + { + "type": "string" + }, + { + "type": "integer" + } + ], + "items": false, + "minItems": 2 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + "item", + 42 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + "item" + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + "item", + 42, + true + ], + "valid": false + }, + { + "label": "invalid_3", + "instance": [ + 4, + 42 + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-unique", + "generatorExpectation": "must-generate", + "featureTags": [ + "uniqueItems", + "minItems", + "maxItems" + ], + "schema": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "integer" + }, + "uniqueItems": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1, + 2, + 3 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + 1, + 1, + 2 + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + 1, + 2 + ], + "valid": false + }, + { + "label": "invalid_3", + "instance": [ + "a", + "b", + "c" + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-contains", + "generatorExpectation": "must-generate", + "featureTags": [ + "contains", + "minContains", + "maxContains" + ], + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "contains": { + "multipleOf": 2 + }, + "minContains": 2, + "maxContains": 2 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1, + 2, + 4 + ], + "valid": true + }, + { + "label": "valid_2", + "instance": [ + 2, + 4, + 5 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + 1, + 3, + 5 + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + 2, + 4, + 6 + ], + "valid": false + }, + { + "label": "invalid_3", + "instance": [ + 1, + 2, + "hi" + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "enum", + "generatorExpectation": "must-generate", + "featureTags": [ + "enum" + ], + "schema": { + "enum": [ + "red", + "green", + "blue" + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "red", + "valid": true + }, + { + "label": "valid_2", + "instance": "green", + "valid": true + }, + { + "label": "invalid_1", + "instance": "purple", + "valid": false + }, + { + "label": "invalid_2", + "instance": null, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "const-exact-decimal", + "generatorExpectation": "must-generate", + "featureTags": [ + "const", + "exactNumber" + ], + "schema": { + "const": 0.1 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.1, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0.2, + "valid": false + }, + { + "label": "invalid_2", + "instance": 1, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "anyof", + "generatorExpectation": "must-generate", + "featureTags": [ + "anyOf" + ], + "schema": { + "anyOf": [ + { + "type": "string", + "minLength": 2 + }, + { + "type": "integer", + "minimum": 8 + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ok", + "valid": true + }, + { + "label": "valid_2", + "instance": 9, + "valid": true + }, + { + "label": "invalid_1", + "instance": "a", + "valid": false + }, + { + "label": "invalid_2", + "instance": 7, + "valid": false + }, + { + "label": "invalid_3", + "instance": true, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "oneof-overlap", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "oneOf", + "overlap" + ], + "schema": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "integer" + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.5, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0, + "valid": false + }, + { + "label": "invalid_2", + "instance": 1, + "valid": false + }, + { + "label": "invalid_3", + "instance": "abc", + "valid": false + } + ], + "notes": "A number that is mathematically integer satisfies both branches and is invalid under oneOf", + "acceptanceGate": "G4" + }, + { + "id": "allof-object-composition", + "generatorExpectation": "must-generate", + "featureTags": [ + "allOf", + "object" + ], + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "a": { + "type": "integer" + } + }, + "required": [ + "a" + ] + }, + { + "properties": { + "b": { + "type": "string" + } + }, + "required": [ + "b" + ] + } + ], + "additionalProperties": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "a": 1, + "b": "x" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "a": 1 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "a": "wrong", + "b": "x" + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "condition-then-else", + "generatorExpectation": "must-generate", + "featureTags": [ + "if", + "then", + "else", + "const", + "required" + ], + "schema": { + "type": "object", + "properties": { + "kind": { + "enum": [ + "adult", + "child" + ] + }, + "age": { + "type": "integer" + } + }, + "required": [ + "kind", + "age" + ], + "if": { + "properties": { + "kind": { + "const": "adult" + } + }, + "required": [ + "kind" + ] + }, + "then": { + "properties": { + "age": { + "minimum": 18 + } + } + }, + "else": { + "properties": { + "age": { + "maximum": 17 + } + } + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "kind": "adult", + "age": 18 + }, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "kind": "child", + "age": 12 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "kind": "adult", + "age": 17 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "kind": "child", + "age": 18 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "dependent-required", + "generatorExpectation": "must-generate", + "featureTags": [ + "dependentRequired" + ], + "schema": { + "type": "object", + "properties": { + "creditCard": { + "type": "string" + }, + "billingAddress": { + "type": "string" + } + }, + "dependentRequired": { + "creditCard": [ + "billingAddress" + ] + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": {}, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "creditCard": "1234", + "billingAddress": "x" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "creditCard": "1234" + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "dependent-schema", + "generatorExpectation": "must-generate", + "featureTags": [ + "dependentSchemas" + ], + "schema": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "state": { + "type": "string" + } + }, + "dependentSchemas": { + "country": { + "required": [ + "state" + ] + } + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": {}, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "country": "US", + "state": "CA" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "country": "US" + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "pattern-properties", + "generatorExpectation": "must-generate", + "featureTags": [ + "patternProperties", + "additionalProperties" + ], + "schema": { + "type": "object", + "patternProperties": { + "^s_": { + "type": "string" + } + }, + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "s_name": "Ada" + }, + "valid": true + }, + { + "label": "valid_2", + "instance": {}, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "s_name": 42 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "x": 2 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "property-names", + "generatorExpectation": "must-generate", + "featureTags": [ + "propertyNames", + "pattern" + ], + "schema": { + "type": "object", + "propertyNames": { + "pattern": "^[A-Z]+$" + }, + "additionalProperties": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "ABC": 1 + }, + "valid": true + }, + { + "label": "valid_2", + "instance": {}, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "Ab": 1 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "unevaluated-properties-composition", + "generatorExpectation": "must-generate", + "featureTags": [ + "unevaluatedProperties", + "allOf", + "annotations" + ], + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "a": { + "type": "integer" + } + }, + "required": [ + "a" + ] + }, + { + "properties": { + "b": { + "type": "string" + } + }, + "required": [ + "b" + ] + } + ], + "unevaluatedProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "a": 1, + "b": "x" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "a": 1, + "b": "x", + "c": true + }, + "valid": false + } + ], + "notes": "Successful branches must contribute evaluated-property annotations", + "acceptanceGate": "G4" + }, + { + "id": "local-ref", + "generatorExpectation": "must-generate", + "featureTags": [ + "$ref", + "$defs" + ], + "schema": { + "$defs": { + "age": { + "type": "integer", + "minimum": 0 + } + }, + "$ref": "#/$defs/age" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 18, + "valid": true + }, + { + "label": "invalid_1", + "instance": -1, + "valid": false + }, + { + "label": "invalid_2", + "instance": "18", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "recursive-tree", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "$ref", + "recursion" + ], + "schema": { + "$defs": { + "node": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/node" + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "$ref": "#/$defs/node" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "name": "Ada", + "children": [] + }, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "name": "root", + "children": [ + { + "name": "leaf" + } + ] + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "children": [] + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "dynamic-ref", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "$dynamicRef", + "$dynamicAnchor", + "recursion" + ], + "schema": { + "$dynamicAnchor": "node", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$dynamicRef": "#node" + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "name": "root", + "children": [] + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "children": [] + }, + "valid": false + } + ], + "notes": "Dynamic reference has evaluation-scope-dependent meaning", + "acceptanceGate": "G4" + }, + { + "id": "simple-regex", + "generatorExpectation": "must-generate", + "featureTags": [ + "pattern", + "regex" + ], + "schema": { + "type": "string", + "pattern": "^SKU-[0-9]{4}$" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "SKU-1234", + "valid": true + }, + { + "label": "invalid_1", + "instance": "sku-1234", + "valid": false + }, + { + "label": "invalid_2", + "instance": "SKU-123", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "format-annotation", + "generatorExpectation": "must-generate", + "featureTags": [ + "format", + "annotation" + ], + "schema": { + "type": "string", + "format": "email" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "not-an-email", + "valid": true + }, + { + "label": "valid_2", + "instance": "ada@example.org", + "valid": true + }, + { + "label": "invalid_1", + "instance": 15, + "valid": false + } + ], + "notes": "Format keyword is annotation-only under default 2020-12 dialect", + "acceptanceGate": "G3" + }, + { + "id": "format-assertion", + "generatorExpectation": "must-generate", + "featureTags": [ + "format", + "assertion" + ], + "schema": { + "type": "string", + "format": "email" + }, + "validation": { + "formatAssertion": true + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ada@example.org", + "valid": true + }, + { + "label": "invalid_1", + "instance": "not-an-email", + "valid": false + }, + { + "label": "invalid_2", + "instance": 15, + "valid": false + } + ], + "notes": "Optional format assertion flag enabled; independent oracle uses Python FormatChecker", + "acceptanceGate": "G3" + }, + { + "id": "conflicting-allof", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "allOf", + "unsatisfiable" + ], + "schema": { + "allOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": "hello", + "valid": false + }, + { + "label": "invalid_2", + "instance": 3, + "valid": false + }, + { + "label": "invalid_3", + "instance": null, + "valid": false + } + ], + "notes": "Conservatively classify search failure unless sound proof implemented", + "acceptanceGate": "G4" + }, + { + "id": "unfulfillable-unique-enum", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "uniqueItems", + "enum", + "unsatisfiable" + ], + "schema": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "uniqueItems": true, + "items": { + "enum": [ + "a", + "b" + ] + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": [ + "a", + "a", + "b" + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + "a", + "b" + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "invalid-default-annotation", + "generatorExpectation": "must-generate", + "featureTags": [ + "default", + "annotation" + ], + "schema": { + "type": "integer", + "minimum": 10, + "default": 3 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 12, + "valid": true + }, + { + "label": "invalid_1", + "instance": 3, + "valid": false + } + ], + "notes": "Default is not proof of validity", + "acceptanceGate": "G3" + }, + { + "id": "invalid-examples-annotation", + "generatorExpectation": "must-generate", + "featureTags": [ + "examples", + "annotation" + ], + "schema": { + "type": "string", + "minLength": 2, + "examples": [ + "a", + "ok" + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ok", + "valid": true + }, + { + "label": "invalid_1", + "instance": "a", + "valid": false + } + ], + "notes": "examples are untrusted candidate hints", + "acceptanceGate": "G3" + }, + { + "id": "false-nested-schema", + "generatorExpectation": "must-generate", + "featureTags": [ + "boolean", + "properties" + ], + "schema": { + "type": "object", + "properties": { + "forbidden": false + }, + "additionalProperties": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": {}, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "other": 1 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "forbidden": null + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "external-offline-ref", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "$ref", + "offlineRegistry", + "uri" + ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "urn:example:definitions:2026#/$defs/sku" + }, + "validation": { + "formatAssertion": false + }, + "resources": { + "urn:example:definitions:2026": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:definitions:2026", + "$defs": { + "sku": { + "type": "string", + "pattern": "^SKU-[0-9]{3}$" + } + } + } + }, + "witnesses": [ + { + "label": "valid_1", + "instance": "SKU-100", + "valid": true + }, + { + "label": "invalid_1", + "instance": "ABC", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "not-string", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "not" + ], + "schema": { + "not": { + "type": "string" + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0, + "valid": true + }, + { + "label": "valid_2", + "instance": null, + "valid": true + }, + { + "label": "valid_3", + "instance": {}, + "valid": true + }, + { + "label": "invalid_1", + "instance": "hello", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "oneof-overlap-limited", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "oneOf", + "overlap" + ], + "schema": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "integer", + "maximum": 10 + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": -1, + "valid": true + }, + { + "label": "valid_2", + "instance": 11, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0, + "valid": false + }, + { + "label": "invalid_2", + "instance": 5, + "valid": false + }, + { + "label": "invalid_3", + "instance": 10, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "object-number-key-strings", + "generatorExpectation": "must-generate", + "featureTags": [ + "object", + "exactKeys" + ], + "schema": { + "type": "object", + "properties": { + "0": { + "type": "integer" + }, + "00": { + "type": "string" + } + }, + "required": [ + "0", + "00" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "0": 1, + "00": "ok" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "0": 1 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "mincontains-with-prefixitems", + "generatorExpectation": "must-generate", + "featureTags": [ + "prefixItems", + "contains", + "minContains", + "maxContains" + ], + "schema": { + "type": "array", + "prefixItems": [ + { + "type": "number" + } + ], + "contains": { + "type": "integer" + }, + "minContains": 1, + "maxContains": 1, + "minItems": 1 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1.5, + 1 + ], + "valid": true + }, + { + "label": "valid_2", + "instance": [ + 1 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + 1, + 2 + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "required-with-additional-properties-false", + "generatorExpectation": "must-generate", + "featureTags": [ + "additionalProperties" + ], + "schema": { + "type": "object", + "properties": { + "x": { + "type": "integer" + } + }, + "required": [ + "x" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "x": 0 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "x": 0, + "extra": true + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-mixed-evaluation", + "generatorExpectation": "must-generate", + "featureTags": [ + "unevaluatedItems", + "prefixItems" + ], + "schema": { + "type": "array", + "prefixItems": [ + { + "type": "integer" + } + ], + "unevaluatedItems": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1 + ], + "valid": true + }, + { + "label": "valid_2", + "instance": [], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + 1, + 2 + ], + "valid": false + } + ], + "notes": "[] also satisfies this schema; oracle negative list should not include []", + "acceptanceGate": "G4" + } + ] +} diff --git a/Tests/Fixtures/SchemaExamples/S3-original-v1.json b/Tests/Fixtures/SchemaExamples/S3-original-v1.json new file mode 100644 index 0000000..d0cedfe --- /dev/null +++ b/Tests/Fixtures/SchemaExamples/S3-original-v1.json @@ -0,0 +1,2147 @@ +{ + "fixtureFormat": "SwiftJSON.SchemaExamples.Cases/1", + "draft": "https://json-schema.org/draft/2020-12/schema", + "intendedFor": "Independent correctness oracle and Codex acceptance gates; not production generator outputs", + "cases": [ + { + "id": "boolean-true", + "generatorExpectation": "must-generate", + "featureTags": [ + "boolean" + ], + "schema": true, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": null, + "valid": true + }, + { + "label": "valid_2", + "instance": {}, + "valid": true + }, + { + "label": "valid_3", + "instance": [], + "valid": true + }, + { + "label": "valid_4", + "instance": 42, + "valid": true + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "boolean-false", + "generatorExpectation": "must-prove-unsatisfiable", + "featureTags": [ + "boolean", + "unsatisfiable" + ], + "schema": false, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": null, + "valid": false + }, + { + "label": "invalid_2", + "instance": {}, + "valid": false + }, + { + "label": "invalid_3", + "instance": 12, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "not-true", + "generatorExpectation": "must-prove-unsatisfiable", + "featureTags": [ + "not", + "unsatisfiable" + ], + "schema": { + "not": {} + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": null, + "valid": false + }, + { + "label": "invalid_2", + "instance": {}, + "valid": false + }, + { + "label": "invalid_3", + "instance": "x", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "null-kind", + "generatorExpectation": "must-generate", + "featureTags": [ + "type" + ], + "schema": { + "type": "null" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": null, + "valid": true + }, + { + "label": "invalid_1", + "instance": false, + "valid": false + }, + { + "label": "invalid_2", + "instance": 0, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "string-length", + "generatorExpectation": "must-generate", + "featureTags": [ + "string", + "unicode", + "minLength", + "maxLength" + ], + "schema": { + "type": "string", + "minLength": 2, + "maxLength": 4 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ab", + "valid": true + }, + { + "label": "valid_2", + "instance": "abc", + "valid": true + }, + { + "label": "valid_3", + "instance": "é", + "valid": true + }, + { + "label": "invalid_1", + "instance": "", + "valid": false + }, + { + "label": "invalid_2", + "instance": "é", + "valid": false + }, + { + "label": "invalid_3", + "instance": "abcde", + "valid": false + }, + { + "label": "invalid_4", + "instance": 7, + "valid": false + } + ], + "notes": "Length is Unicode scalars, not grapheme clusters", + "acceptanceGate": "G3" + }, + { + "id": "exact-integer-beyond-2pow53", + "generatorExpectation": "must-generate", + "featureTags": [ + "integer", + "exactNumber" + ], + "schema": { + "type": "integer", + "minimum": 9007199254740993, + "maximum": 9007199254740995 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 9007199254740993, + "valid": true + }, + { + "label": "valid_2", + "instance": 9007199254740994, + "valid": true + }, + { + "label": "invalid_1", + "instance": 9007199254740992, + "valid": false + }, + { + "label": "invalid_2", + "instance": 9007199254740996, + "valid": false + }, + { + "label": "invalid_3", + "instance": 9007199254740994.0, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "exact-decimal-multiple-of", + "generatorExpectation": "must-generate", + "featureTags": [ + "exactNumber", + "multipleOf", + "exclusiveMaximum" + ], + "schema": { + "type": "number", + "multipleOf": 0.01, + "minimum": 0.02, + "exclusiveMaximum": 0.05 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.02, + "valid": true + }, + { + "label": "valid_2", + "instance": 0.03, + "valid": true + }, + { + "label": "valid_3", + "instance": 0.04, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0.05, + "valid": false + }, + { + "label": "invalid_2", + "instance": 0.025, + "valid": false + }, + { + "label": "invalid_3", + "instance": 0.01, + "valid": false + } + ], + "notes": "Parse JSON numeric fractions as Decimal for independent Python oracle", + "acceptanceGate": "G3" + }, + { + "id": "numeric-exclusive-near-zero", + "generatorExpectation": "must-generate", + "featureTags": [ + "exactNumber", + "multipleOf", + "exclusiveMinimum", + "exclusiveMaximum" + ], + "schema": { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 0.001, + "multipleOf": 0.0001 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.0001, + "valid": true + }, + { + "label": "valid_2", + "instance": 0.0005, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0, + "valid": false + }, + { + "label": "invalid_2", + "instance": 0.001, + "valid": false + }, + { + "label": "invalid_3", + "instance": 0.00015, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "object-required-and-null", + "generatorExpectation": "must-generate", + "featureTags": [ + "object", + "required", + "null", + "additionalProperties" + ], + "schema": { + "type": "object", + "properties": { + "x": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "x" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "x": null + }, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "x": "abc" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": {}, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "x": 1 + }, + "valid": false + }, + { + "label": "invalid_3", + "instance": { + "x": "a", + "y": 0 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "unicode-exact-scalar-keys", + "generatorExpectation": "must-generate", + "featureTags": [ + "unicode", + "object", + "required", + "exactKeys" + ], + "schema": { + "type": "object", + "properties": { + "é": { + "const": 1 + }, + "é": { + "const": 2 + } + }, + "required": [ + "é", + "é" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "é": 1, + "é": 2 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "é": 1 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "é": 2 + }, + "valid": false + }, + { + "label": "invalid_3", + "instance": { + "é": 2, + "é": 1 + }, + "valid": false + } + ], + "notes": "Two different decoded scalar sequences, even when canonically equivalent", + "acceptanceGate": "G3" + }, + { + "id": "undeclared-required-key", + "generatorExpectation": "must-generate", + "featureTags": [ + "required", + "additionalProperties" + ], + "schema": { + "type": "object", + "required": [ + "id" + ], + "additionalProperties": { + "type": "integer" + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "id": 0 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": {}, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "id": null + }, + "valid": false + } + ], + "notes": "Required key need not have a properties declaration", + "acceptanceGate": "G3" + }, + { + "id": "object-min-max-properties", + "generatorExpectation": "must-generate", + "featureTags": [ + "minProperties", + "maxProperties", + "additionalProperties" + ], + "schema": { + "type": "object", + "minProperties": 2, + "maxProperties": 2, + "additionalProperties": { + "type": "integer" + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "a": 1, + "b": 2 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": {}, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "a": 1 + }, + "valid": false + }, + { + "label": "invalid_3", + "instance": { + "a": 1, + "b": 2, + "c": 3 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-prefix-items", + "generatorExpectation": "must-generate", + "featureTags": [ + "prefixItems", + "items", + "minItems" + ], + "schema": { + "type": "array", + "prefixItems": [ + { + "type": "string" + }, + { + "type": "integer" + } + ], + "items": false, + "minItems": 2 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + "item", + 42 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + "item" + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + "item", + 42, + true + ], + "valid": false + }, + { + "label": "invalid_3", + "instance": [ + 4, + 42 + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-unique", + "generatorExpectation": "must-generate", + "featureTags": [ + "uniqueItems", + "minItems", + "maxItems" + ], + "schema": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "integer" + }, + "uniqueItems": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1, + 2, + 3 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + 1, + 1, + 2 + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + 1, + 2 + ], + "valid": false + }, + { + "label": "invalid_3", + "instance": [ + "a", + "b", + "c" + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-contains", + "generatorExpectation": "must-generate", + "featureTags": [ + "contains", + "minContains", + "maxContains" + ], + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "contains": { + "multipleOf": 2 + }, + "minContains": 2, + "maxContains": 2 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1, + 2, + 4 + ], + "valid": true + }, + { + "label": "valid_2", + "instance": [ + 2, + 4, + 5 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + 1, + 3, + 5 + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + 2, + 4, + 6 + ], + "valid": false + }, + { + "label": "invalid_3", + "instance": [ + 1, + 2, + "hi" + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "enum", + "generatorExpectation": "must-generate", + "featureTags": [ + "enum" + ], + "schema": { + "enum": [ + "red", + "green", + "blue" + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "red", + "valid": true + }, + { + "label": "valid_2", + "instance": "green", + "valid": true + }, + { + "label": "invalid_1", + "instance": "purple", + "valid": false + }, + { + "label": "invalid_2", + "instance": null, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "const-exact-decimal", + "generatorExpectation": "must-generate", + "featureTags": [ + "const", + "exactNumber" + ], + "schema": { + "const": 0.1 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.1, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0.2, + "valid": false + }, + { + "label": "invalid_2", + "instance": 1, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "anyof", + "generatorExpectation": "must-generate", + "featureTags": [ + "anyOf" + ], + "schema": { + "anyOf": [ + { + "type": "string", + "minLength": 2 + }, + { + "type": "integer", + "minimum": 8 + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ok", + "valid": true + }, + { + "label": "valid_2", + "instance": 9, + "valid": true + }, + { + "label": "invalid_1", + "instance": "a", + "valid": false + }, + { + "label": "invalid_2", + "instance": 7, + "valid": false + }, + { + "label": "invalid_3", + "instance": true, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "oneof-overlap", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "oneOf", + "overlap" + ], + "schema": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "integer" + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0.5, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0, + "valid": false + }, + { + "label": "invalid_2", + "instance": 1, + "valid": false + }, + { + "label": "invalid_3", + "instance": "abc", + "valid": false + } + ], + "notes": "A number that is mathematically integer satisfies both branches and is invalid under oneOf", + "acceptanceGate": "G4" + }, + { + "id": "allof-object-composition", + "generatorExpectation": "must-generate", + "featureTags": [ + "allOf", + "object" + ], + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "a": { + "type": "integer" + } + }, + "required": [ + "a" + ] + }, + { + "properties": { + "b": { + "type": "string" + } + }, + "required": [ + "b" + ] + } + ], + "additionalProperties": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "a": 1, + "b": "x" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "a": 1 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "a": "wrong", + "b": "x" + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "condition-then-else", + "generatorExpectation": "must-generate", + "featureTags": [ + "if", + "then", + "else", + "const", + "required" + ], + "schema": { + "type": "object", + "properties": { + "kind": { + "enum": [ + "adult", + "child" + ] + }, + "age": { + "type": "integer" + } + }, + "required": [ + "kind", + "age" + ], + "if": { + "properties": { + "kind": { + "const": "adult" + } + }, + "required": [ + "kind" + ] + }, + "then": { + "properties": { + "age": { + "minimum": 18 + } + } + }, + "else": { + "properties": { + "age": { + "maximum": 17 + } + } + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "kind": "adult", + "age": 18 + }, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "kind": "child", + "age": 12 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "kind": "adult", + "age": 17 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "kind": "child", + "age": 18 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "dependent-required", + "generatorExpectation": "must-generate", + "featureTags": [ + "dependentRequired" + ], + "schema": { + "type": "object", + "properties": { + "creditCard": { + "type": "string" + }, + "billingAddress": { + "type": "string" + } + }, + "dependentRequired": { + "creditCard": [ + "billingAddress" + ] + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": {}, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "creditCard": "1234", + "billingAddress": "x" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "creditCard": "1234" + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "dependent-schema", + "generatorExpectation": "must-generate", + "featureTags": [ + "dependentSchemas" + ], + "schema": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "state": { + "type": "string" + } + }, + "dependentSchemas": { + "country": { + "required": [ + "state" + ] + } + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": {}, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "country": "US", + "state": "CA" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "country": "US" + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "pattern-properties", + "generatorExpectation": "must-generate", + "featureTags": [ + "patternProperties", + "additionalProperties" + ], + "schema": { + "type": "object", + "patternProperties": { + "^s_": { + "type": "string" + } + }, + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "s_name": "Ada" + }, + "valid": true + }, + { + "label": "valid_2", + "instance": {}, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "s_name": 42 + }, + "valid": false + }, + { + "label": "invalid_2", + "instance": { + "x": 2 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "property-names", + "generatorExpectation": "must-generate", + "featureTags": [ + "propertyNames", + "pattern" + ], + "schema": { + "type": "object", + "propertyNames": { + "pattern": "^[A-Z]+$" + }, + "additionalProperties": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "ABC": 1 + }, + "valid": true + }, + { + "label": "valid_2", + "instance": {}, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "Ab": 1 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "unevaluated-properties-composition", + "generatorExpectation": "must-generate", + "featureTags": [ + "unevaluatedProperties", + "allOf", + "annotations" + ], + "schema": { + "type": "object", + "allOf": [ + { + "properties": { + "a": { + "type": "integer" + } + }, + "required": [ + "a" + ] + }, + { + "properties": { + "b": { + "type": "string" + } + }, + "required": [ + "b" + ] + } + ], + "unevaluatedProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "a": 1, + "b": "x" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "a": 1, + "b": "x", + "c": true + }, + "valid": false + } + ], + "notes": "Successful branches must contribute evaluated-property annotations", + "acceptanceGate": "G4" + }, + { + "id": "local-ref", + "generatorExpectation": "must-generate", + "featureTags": [ + "$ref", + "$defs" + ], + "schema": { + "$defs": { + "age": { + "type": "integer", + "minimum": 0 + } + }, + "$ref": "#/$defs/age" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 18, + "valid": true + }, + { + "label": "invalid_1", + "instance": -1, + "valid": false + }, + { + "label": "invalid_2", + "instance": "18", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "recursive-tree", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "$ref", + "recursion" + ], + "schema": { + "$defs": { + "node": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/node" + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "$ref": "#/$defs/node" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "name": "Ada", + "children": [] + }, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "name": "root", + "children": [ + { + "name": "leaf" + } + ] + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "children": [] + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "dynamic-ref", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "$dynamicRef", + "$dynamicAnchor", + "recursion" + ], + "schema": { + "$dynamicAnchor": "node", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$dynamicRef": "#node" + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "name": "root", + "children": [] + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "children": [] + }, + "valid": false + } + ], + "notes": "Dynamic reference has evaluation-scope-dependent meaning", + "acceptanceGate": "G4" + }, + { + "id": "simple-regex", + "generatorExpectation": "must-generate", + "featureTags": [ + "pattern", + "regex" + ], + "schema": { + "type": "string", + "pattern": "^SKU-[0-9]{4}$" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "SKU-1234", + "valid": true + }, + { + "label": "invalid_1", + "instance": "sku-1234", + "valid": false + }, + { + "label": "invalid_2", + "instance": "SKU-123", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "format-annotation", + "generatorExpectation": "must-generate", + "featureTags": [ + "format", + "annotation" + ], + "schema": { + "type": "string", + "format": "email" + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "not-an-email", + "valid": true + }, + { + "label": "valid_2", + "instance": "ada@example.org", + "valid": true + }, + { + "label": "invalid_1", + "instance": 15, + "valid": false + } + ], + "notes": "Format keyword is annotation-only under default 2020-12 dialect", + "acceptanceGate": "G3" + }, + { + "id": "format-assertion", + "generatorExpectation": "must-generate", + "featureTags": [ + "format", + "assertion" + ], + "schema": { + "type": "string", + "format": "email" + }, + "validation": { + "formatAssertion": true + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ada@example.org", + "valid": true + }, + { + "label": "invalid_1", + "instance": "not-an-email", + "valid": false + }, + { + "label": "invalid_2", + "instance": 15, + "valid": false + } + ], + "notes": "Optional format assertion flag enabled; independent oracle uses Python FormatChecker", + "acceptanceGate": "G3" + }, + { + "id": "conflicting-allof", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "allOf", + "unsatisfiable" + ], + "schema": { + "allOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": "hello", + "valid": false + }, + { + "label": "invalid_2", + "instance": 3, + "valid": false + }, + { + "label": "invalid_3", + "instance": null, + "valid": false + } + ], + "notes": "Conservatively classify search failure unless sound proof implemented", + "acceptanceGate": "G4" + }, + { + "id": "unfulfillable-unique-enum", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "uniqueItems", + "enum", + "unsatisfiable" + ], + "schema": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "uniqueItems": true, + "items": { + "enum": [ + "a", + "b" + ] + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "invalid_1", + "instance": [ + "a", + "a", + "b" + ], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + "a", + "b" + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "invalid-default-annotation", + "generatorExpectation": "must-generate", + "featureTags": [ + "default", + "annotation" + ], + "schema": { + "type": "integer", + "minimum": 10, + "default": 3 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 12, + "valid": true + }, + { + "label": "invalid_1", + "instance": 3, + "valid": false + } + ], + "notes": "Default is not proof of validity", + "acceptanceGate": "G3" + }, + { + "id": "invalid-examples-annotation", + "generatorExpectation": "must-generate", + "featureTags": [ + "examples", + "annotation" + ], + "schema": { + "type": "string", + "minLength": 2, + "examples": [ + "a", + "ok" + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": "ok", + "valid": true + }, + { + "label": "invalid_1", + "instance": "a", + "valid": false + } + ], + "notes": "examples are untrusted candidate hints", + "acceptanceGate": "G3" + }, + { + "id": "false-nested-schema", + "generatorExpectation": "must-generate", + "featureTags": [ + "boolean", + "properties" + ], + "schema": { + "type": "object", + "properties": { + "forbidden": false + }, + "additionalProperties": true + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": {}, + "valid": true + }, + { + "label": "valid_2", + "instance": { + "other": 1 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "forbidden": null + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "external-offline-ref", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "$ref", + "offlineRegistry", + "uri" + ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "urn:example:definitions:2026#/$defs/sku" + }, + "validation": { + "formatAssertion": false + }, + "resources": { + "urn:example:definitions:2026": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:definitions:2026", + "$defs": { + "sku": { + "type": "string", + "pattern": "^SKU-[0-9]{3}$" + } + } + } + }, + "witnesses": [ + { + "label": "valid_1", + "instance": "SKU-100", + "valid": true + }, + { + "label": "invalid_1", + "instance": "ABC", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "not-string", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "not" + ], + "schema": { + "not": { + "type": "string" + } + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": 0, + "valid": true + }, + { + "label": "valid_2", + "instance": null, + "valid": true + }, + { + "label": "valid_3", + "instance": {}, + "valid": true + }, + { + "label": "invalid_1", + "instance": "hello", + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "oneof-overlap-limited", + "generatorExpectation": "may-fail-explicitly", + "featureTags": [ + "oneOf", + "overlap" + ], + "schema": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "integer", + "maximum": 10 + } + ] + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": -1, + "valid": true + }, + { + "label": "valid_2", + "instance": 11, + "valid": true + }, + { + "label": "invalid_1", + "instance": 0, + "valid": false + }, + { + "label": "invalid_2", + "instance": 5, + "valid": false + }, + { + "label": "invalid_3", + "instance": 10, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "object-number-key-strings", + "generatorExpectation": "must-generate", + "featureTags": [ + "object", + "exactKeys" + ], + "schema": { + "type": "object", + "properties": { + "0": { + "type": "integer" + }, + "00": { + "type": "string" + } + }, + "required": [ + "0", + "00" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "0": 1, + "00": "ok" + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "0": 1 + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "mincontains-with-prefixitems", + "generatorExpectation": "must-generate", + "featureTags": [ + "prefixItems", + "contains", + "minContains", + "maxContains" + ], + "schema": { + "type": "array", + "prefixItems": [ + { + "type": "number" + } + ], + "contains": { + "type": "integer" + }, + "minContains": 1, + "maxContains": 1, + "minItems": 1 + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1.5, + 1 + ], + "valid": true + }, + { + "label": "valid_2", + "instance": [ + 1 + ], + "valid": true + }, + { + "label": "invalid_1", + "instance": [], + "valid": false + }, + { + "label": "invalid_2", + "instance": [ + 1, + 2 + ], + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G4" + }, + { + "id": "required-with-additional-properties-false", + "generatorExpectation": "must-generate", + "featureTags": [ + "additionalProperties" + ], + "schema": { + "type": "object", + "properties": { + "x": { + "type": "integer" + } + }, + "required": [ + "x" + ], + "additionalProperties": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": { + "x": 0 + }, + "valid": true + }, + { + "label": "invalid_1", + "instance": { + "x": 0, + "extra": true + }, + "valid": false + } + ], + "notes": "", + "acceptanceGate": "G3" + }, + { + "id": "array-mixed-evaluation", + "generatorExpectation": "must-generate", + "featureTags": [ + "unevaluatedItems", + "prefixItems" + ], + "schema": { + "type": "array", + "prefixItems": [ + { + "type": "integer" + } + ], + "unevaluatedItems": false + }, + "validation": { + "formatAssertion": false + }, + "resources": {}, + "witnesses": [ + { + "label": "valid_1", + "instance": [ + 1 + ], + "valid": true + }, + { + "label": "valid_2", + "instance": [], + "valid": true + }, + { + "label": "invalid_1", + "instance": [ + 1, + 2 + ], + "valid": false + } + ], + "notes": "[] also satisfies this schema; oracle negative list should not include []", + "acceptanceGate": "G4" + } + ] +} diff --git a/Tests/JSONSchemaExamplesTests/AdvancedConstructionTests.swift b/Tests/JSONSchemaExamplesTests/AdvancedConstructionTests.swift new file mode 100644 index 0000000..b9eec56 --- /dev/null +++ b/Tests/JSONSchemaExamplesTests/AdvancedConstructionTests.swift @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONSchema +@testable import JSONSchemaExamples +import Testing + +private func prepared(_ source: String, options: JSONSchemaExampleOptions = .init()) throws -> JSONSchemaExampleGenerator { + try .init(document: .init(JSONParser().parse(source)), options: options) +} +private func cases() throws -> [JSON] { + let url = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent() + .appendingPathComponent("Fixtures/SchemaExamples/S3-integral-correction-v1.json") + return try #require(JSONParser().parse(Data(contentsOf: url)).objectValue?["cases"]?.arrayValue) +} +private func registry(_ c: JSONObject) throws -> JSONSchemaRegistry { + var documents: [JSONSchemaDocument] = [] + if let resources = c["resources"]?.objectValue { + for member in resources { documents.append(try .init(member.value, retrievalURI: JSONSchemaURI(member.key.string))) } + } + return try .init(documents: documents) +} +private func resource(_ error: any Error, _ metric: JSONResourceMetric) -> Bool { + guard let error = error as? JSONFailure else { return false } + return error.category == .resource && error.resource?.metric == metric +} + +@Test func g4AllTenRequiredAndEightIncompleteFixturesHaveHonestNativeOutcomes() throws { + var required: [String] = [], incomplete: [String] = [], successes = 0, failures = 0, witnesses = 0, mustSuccesses = 0 + for value in try cases() { + let c = try #require(value.objectValue) + guard c["acceptanceGate"] == .string("G4") else { continue } + let id = try #require(c["id"]?.stringValue) + let must = c["generatorExpectation"] == .string("must-generate") + let options = JSONSchemaExampleOptions(validationOptions: .init(formatAssertion: c["validation"]?.objectValue?["formatAssertion"]?.booleanValue == true)) + let generator = try JSONSchemaExampleGenerator(document: .init(try #require(c["schema"])), registry: registry(c), options: options) + let plan = try JSONSchemaCompiler(options: options.compilationOptions).compile(generator.document, registry: generator.registry) + let validator = JSONSchemaValidator(options: options.validationOptions) + for witness in try #require(c["witnesses"]?.arrayValue) { + #expect(try validator.validate(#require(witness.objectValue?["instance"]), against: plan).isValid == #require(witness.objectValue?["valid"]?.booleanValue as Bool?)) + witnesses += 1 + } + var valid = 0, unsupported = 0, exhausted = 0 + for profile in [JSONSchemaExampleProfile.minimal, .demonstration] { + for seed in [UInt64(0),1,42,UInt64.max] { + do { + let result = try generator.generate(seed: seed, profile: profile) + #expect(result.validation.isValid && result.validation.diagnosticsComplete) + #expect(try validator.validate(result.value, against: plan).isValid) + #expect(try JSONParser().parse(JSONWriter().write(result.value)) == result.value) + #expect(result.statistics[.workUnits] <= options.limits[.workUnits]) + successes += 1; valid += 1; if must { mustSuccesses += 1 } + } catch let failure as JSONSchemaExampleFailure { + #expect(!must, "Required G4 fixture failed: \(id)") + switch failure { + case .unsupportedFeature: unsupported += 1 + case .searchExhausted(let attempts): #expect(attempts > 0 && attempts <= options.maximumAttempts); exhausted += 1 + default: Issue.record("Incomplete search became a proof/configuration error: \(id)") + } + failures += 1 + } + // Native operational errors/cancellation escape and fail the test. + } + } + if must { required.append(id) } else { incomplete.append(id) } + print("G4_FIXTURE id=\(id) must=\(must) valid=\(valid) unsupported=\(unsupported) exhausted=\(exhausted)") + } + #expect(required == ["array-contains","condition-then-else","dependent-required","dependent-schema","pattern-properties","property-names","unevaluated-properties-composition","simple-regex","mincontains-with-prefixitems","array-mixed-evaluation"]) + #expect(incomplete == ["oneof-overlap","recursive-tree","dynamic-ref","conflicting-allof","unfulfillable-unique-enum","external-offline-ref","not-string","oneof-overlap-limited"]) + #expect(mustSuccesses == 80 && successes + failures == 144 && witnesses == 59) + print("G4_ACCEPTANCE passed required=10 incomplete=8 must_valid=80 valid=\(successes) explicit=\(failures) witnesses=59") +} + +@Test func g4AnchoredLiteralRangeAndExactRepeatProposalsAreNativeCertified() throws { + for (pattern, expected) in [("^SKU-[0-9]{4}$","SKU-0000"), ("^[A-C]{2}X$","AAX"), (#"^a\+b\?$"#,"a+b?"), ("^$","")] { + let raw = try JSONObject(members: [.init("type",value: .string("string")),.init("pattern",value: .string(pattern))]) + let result = try JSONSchemaExampleGenerator(document: .init(.object(raw))).generate() + #expect(result.value == .string(expected) && result.validation.isValid) + } + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + #expect(try SchemaExamplePattern.propose("^[A-C]{2}X$", variation: 2, minimum: 0, maximum: 4, meter: &meter) == "CCX") +} + +@Test func g4UnsupportedRegexSyntaxNeverPretendsToBeAnInverter() throws { + var count = 0 + for pattern in ["SKU-[0-9]{4}","^(a|b)$","^(a+)+$",#"^(a)\1$"#,"^(?=a)a$","^[^a]$","^[ab]$","^a{1,3}$","^a?$","^é$"] { + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + #expect(try SchemaExamplePattern.propose(pattern, variation: 0, minimum: 0, maximum: 1024, meter: &meter) == nil) + #expect(meter.used(.workUnits) > 0); count += 1 + } + #expect(count == 10) +} + +@Test func g4UnsupportedPatternsCanStillUseAdmittedCorpusWithoutFallbackEngine() throws { + let source = #"{"type":"string","pattern":"^(ab)+$"}"# + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 3)) { try prepared(source, options: .init(maximumAttempts: 3)).generate() } + let generated = try prepared(source, options: .init(corpus: .init(values: [.string("ab")]))).generate(profile: .demonstration) + #expect(generated.value == .string("ab") && generated.validation.isValid) + let unicode = try prepared(#"{"type":"string","pattern":"^é$"}"#, options: .init(corpus: .init(values: [.string("é")]))).generate(profile: .demonstration) + #expect(unicode.value == .string("é") && unicode.validation.isValid) + let plan = try JSONSchemaCompiler().compile(JSONSchemaDocument(JSONParser().parse(#"{"pattern":"^é$"}"#))) + #expect(try !JSONSchemaValidator().validate(.string("e\u{301}"), against: plan).isValid) +} + +@Test func g4ConjunctivePatternsAreNeverOverwrittenOrLocallyCertified() throws { + let conflict = #"{"type":"string","allOf":[{"pattern":"^A[0-9]$"},{"pattern":"^B[0-9]$"}]}"# + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 3)) { try prepared(conflict, options: .init(maximumAttempts: 3)).generate() } + let compatible = #"{"type":"string","allOf":[{"pattern":"^A[0-9]$"},{"pattern":"^A0$"}]}"# + #expect(try prepared(compatible).generate().value == .string("A0")) +} + +@Test func g4ContainsDefaultZeroAndInactiveSiblingLimitsRemainDistinct() throws { + #expect(try prepared(#"{"type":"array","items":{"type":"integer"},"contains":{"multipleOf":2}}"#).generate().value == .array([0])) + #expect(try prepared(#"{"type":"array","contains":false,"minContains":0,"maxContains":0}"#).generate().value == .array([])) + #expect(try prepared(#"{"type":"array","minContains":1000,"maxContains":0}"#).generate().value == .array([])) +} + +@Test func g4ContainsPrefixAndUniqueConstraintsShareTheRootCertificate() throws { + let source = #"{"type":"array","prefixItems":[{"const":2}],"items":{"type":"integer"},"minItems":2,"uniqueItems":true,"contains":{"multipleOf":2},"minContains":2,"maxContains":2}"# + let result = try prepared(source).generate() + #expect(result.value == .array([2,4]) && result.attempts == 2 && result.validation.isValid) + let conjuncts = #"{"type":"array","items":{"type":"integer"},"allOf":[{"contains":{"minimum":2},"minContains":2},{"contains":{"multipleOf":2},"minContains":2}]}"# + #expect(try prepared(conjuncts).generate().value == .array([2,2])) + let evaluated = #"{"type":"array","contains":{"const":1},"minContains":2,"unevaluatedItems":false}"# + #expect(try prepared(evaluated).generate().value == .array([1,1])) +} + +@Test func g4ExtraContainsMatchesRemainHonestSearchExhaustion() throws { + let source = #"{"type":"array","minItems":3,"items":{"const":2},"contains":{"type":"integer"},"minContains":1,"maxContains":1}"# + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 3)) { try prepared(source, options: .init(maximumAttempts: 3)).generate() } + let falseContains = #"{"type":"array","contains":false}"# + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction)) { try prepared(falseContains).generate() } + let unevaluated = #"{"type":"array","prefixItems":[{"const":2}],"minItems":2,"unevaluatedItems":false}"# + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 3)) { try prepared(unevaluated, options: .init(maximumAttempts: 3)).generate() } +} + +@Test func g4ContainsCardinalityIsCheckedBeforeItemStaging() throws { + var limits = JSONLimits.balanced; limits[.arrayItems] = 1 + do { _ = try prepared(#"{"type":"array","contains":true,"minContains":2}"#, options: .init(limits: limits)).generate(); Issue.record("Contains count bypassed array cap") } + catch { #expect(resource(error,.arrayItems)) } +} + +@Test func g4RegexOperationalFailureIsTerminalRatherThanAnInvalidCandidate() throws { + do { _ = try prepared(#"{"pattern":"["}"#); Issue.record("Malformed regex reached proposal construction") } + catch let failure as JSONSchemaCompilationFailure { #expect(failure.category == .schemaSyntax) } + var limits = JSONLimits.balanced; limits[.regexSearchWorkUnits] = 0 + do { _ = try prepared(#"{"type":"string","pattern":"^SKU-[0-9]{4}$"}"#, options: .init(validationOptions: .init(limits: limits))).generate(); Issue.record("Regex operation retried or certified") } + catch { #expect(resource(error,.regexSearchWorkUnits)) } + limits = .balanced; limits[.regexCompileWorkUnits] = 0 + do { _ = try prepared(#"{"pattern":"a"}"#, options: .init(compilationOptions: .init(limits: limits))); Issue.record("Regex compile error became search failure") } + catch { #expect(resource(error,.regexCompileWorkUnits)) } +} + +@Test func g4PatternScannerHasExactWorkAndPreallocationBoundaries() throws { + var measure = try JSONWorkMeter(limits: .balanced, domain: .core) + #expect(try SchemaExamplePattern.propose("^SKU-[0-9]{4}$", variation: 0, minimum: 0, maximum: 100, meter: &measure) == "SKU-0000") + var limits = JSONLimits.balanced; limits[.workUnits] = measure.used(.workUnits) + var exact = try JSONWorkMeter(limits: limits, domain: .core) + #expect(try SchemaExamplePattern.propose("^SKU-[0-9]{4}$", variation: 0, minimum: 0, maximum: 100, meter: &exact) == "SKU-0000") + limits[.workUnits] -= 1 + var short = try JSONWorkMeter(limits: limits, domain: .core) + do { _ = try SchemaExamplePattern.propose("^SKU-[0-9]{4}$", variation: 0, minimum: 0, maximum: 100, meter: &short); Issue.record("Scanner work reset") } + catch { #expect(resource(error,.workUnits)) } + for metric in [JSONResourceMetric.stringBytes,.regexPatternBytes] { + limits = .balanced; limits[metric] = 3 + var meter = try JSONWorkMeter(limits: limits, domain: .core) + do { _ = try SchemaExamplePattern.propose("^A{4}$", variation: 0, minimum: 0, maximum: 100, meter: &meter); Issue.record("Scanner growth cap ignored") } + catch { #expect(resource(error,metric)) } + } +} + +@Test func g4CancellationDuringPatternScanningLatchesTheInvocation() async throws { + let task = Task { + var limits = JSONLimits.balanced; limits[.cancellationQuantum] = 1 + var meter = try JSONWorkMeter(limits: limits, domain: .core) + meter.checkpointObserver = { work in if work >= 10 { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { try SchemaExamplePattern.propose("^" + String(repeating: "a",count: 256) + "$", variation: 0, minimum: 0, maximum: 1024, meter: &meter) } + #expect(throws: CancellationError.self) { try meter.work() } + } + try await task.value +} + +@Test func g4ConditionalFalseVerdictConsumesTheCumulativeEvaluationBudget() throws { + let c = try #require(cases().first { $0.objectValue?["id"] == .string("condition-then-else") }?.objectValue) + let document = try JSONSchemaDocument(#require(c["schema"])), generator = try JSONSchemaExampleGenerator(document: document) + let result = try generator.generate(), plan = try JSONSchemaCompiler().compile(document) + let rejected = try JSONSchemaValidator().validateWithReport(JSONParser().parse(#"{"age":0,"kind":"adult"}"#), against: plan) + let accepted = try JSONSchemaValidator().validateWithReport(result.value, against: plan) + #expect(!rejected.result.isValid && accepted.result.isValid && result.attempts == 2) + #expect(result.value.objectValue?["kind"] == .string("child")) + #expect(result.statistics[.evaluationWorkUnits] == rejected.statistics[.workUnits] + accepted.statistics[.workUnits]) + var limits = JSONLimits.balanced; limits[.evaluationWorkUnits] = result.statistics[.evaluationWorkUnits] + #expect(try JSONSchemaExampleGenerator(document: document, options: .init(validationOptions: .init(limits: limits))).generate().validation.isValid) + limits[.evaluationWorkUnits] -= 1 + do { _ = try JSONSchemaExampleGenerator(document: document, options: .init(validationOptions: .init(limits: limits))).generate(); Issue.record("False conditional verdict work refunded") } + catch { #expect(resource(error,.evaluationWorkUnits)) } +} + +@Test func g4ExternalReferencesKeepResourceBasesAndAnchorsQualified() throws { + let a = try JSONSchemaDocument(JSONParser().parse(##"{"$id":"urn:catalog:a","$defs":{"item":{"$anchor":"entry","const":"A"}}}"##), retrievalURI: JSONSchemaURI("urn:catalog:a")) + let b = try JSONSchemaDocument(JSONParser().parse(##"{"$id":"urn:catalog:b","$defs":{"item":{"$id":"urn:catalog:nested","$anchor":"entry","const":"B"}}}"##), retrievalURI: JSONSchemaURI("urn:catalog:b")) + let root = try JSONSchemaDocument(JSONParser().parse(##"{"type":"object","required":["a","b"],"properties":{"a":{"$ref":"urn:catalog:a#entry"},"b":{"$ref":"urn:catalog:nested#entry"}}}"##)) + let generator = try JSONSchemaExampleGenerator(document: root, registry: .init(documents: [a,b])) + let result = try generator.generate() + #expect(result.value.objectValue?["a"] == .string("A") && result.value.objectValue?["b"] == .string("B")) + #expect(result.validation.isValid) +} + +@Test func g4MissingOfflineResourceRemainsACompilationError() throws { + do { _ = try prepared(#"{"$ref":"urn:not-supplied"}"#); Issue.record("Missing resource became candidate search") } + catch let failure as JSONSchemaCompilationFailure { #expect(failure.category == .missingResource) } +} + +@Test func g4ReachedDynamicScopeIsUnsupportedAndRecursiveGrowthIsBounded() throws { + let dynamic = ##"{"$dynamicAnchor":"node","type":"array","minItems":1,"items":{"$dynamicRef":"#node"}}"## + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.dynamicReference)) { try prepared(dynamic).generate() } + var limits = JSONLimits.balanced; limits[.maxDepth] = 8 + let recursive = ##"{"type":"array","minItems":1,"items":{"$ref":"#"}}"## + do { _ = try prepared(recursive, options: .init(limits: limits)).generate(); Issue.record("Recursive instance growth escaped depth cap") } + catch { #expect(resource(error,.maxDepth)) } +} + +@Test func g4IncompletenessNeverBecomesAnUnsatisfiabilityProof() throws { + let overlap = try prepared(#"{"oneOf":[{"type":"number"},{"type":"integer"}]}"#).generate() + #expect(overlap.value == .number(try JSONNumber(parsing: "0.1")) && overlap.attempts == 3) + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction)) { try prepared(#"{"not":{"type":"string"}}"#).generate() } + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction)) { try prepared(#"{"allOf":[{"type":"string"},{"type":"integer"}]}"#).generate() } + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 3)) { try prepared(#"{"type":"array","minItems":3,"maxItems":3,"uniqueItems":true,"items":{"enum":["a","b"]}}"#, options: .init(maximumAttempts: 3)).generate() } + let triggered = #"{"type":"object","required":["a"],"dependentRequired":{"a":["b"]}}"# + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 3)) { try prepared(triggered, options: .init(maximumAttempts: 3)).generate() } +} + +@Test func g4SharedAdvancedGeneratorHasIndependentConcurrentInvocations() async throws { + let generator = try prepared(#"{"type":"array","minItems":2,"uniqueItems":true,"items":{"type":"string"},"contains":{"pattern":"^SKU-[0-9]{4}$"},"minContains":2,"maxContains":2}"#) + let expected = try generator.generate(seed: 42, profile: .demonstration) + try await withThrowingTaskGroup(of: JSONSchemaExampleResult.self) { group in + for _ in 0..<32 { group.addTask { try generator.generate(seed: 42, profile: .demonstration) } } + var count = 0 + for try await result in group { + #expect(result.value == expected.value && result.traceDigest == expected.traceDigest && result.statistics == expected.statistics) + count += 1 + } + #expect(count == 32) + } +} diff --git a/Tests/JSONSchemaExamplesTests/DeterminismTests.swift b/Tests/JSONSchemaExamplesTests/DeterminismTests.swift new file mode 100644 index 0000000..8ce7924 --- /dev/null +++ b/Tests/JSONSchemaExamplesTests/DeterminismTests.swift @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema +@testable import JSONSchemaExamples +import Testing + +private func prepared(_ text: String, options: JSONSchemaExampleOptions = .init()) throws -> JSONSchemaExampleGenerator { + try .init(document: JSONSchemaDocument(JSONParser().parse(text)), options: options) +} +private func testTrace(meter: inout JSONWorkMeter) throws -> SchemaExampleTrace { + try .init(snapshot: "s", corpus: "c", policy: "p", seed: 3, profile: .demonstration, meter: &meter) +} +private func resource(_ error: any Error, _ metric: JSONResourceMetric) -> Bool { + guard let error = error as? JSONFailure else { return false } + return error.category == .resource && error.resource?.metric == metric +} + +@Test func g2MeteredSplitMixMatchesAllReferenceGoldenDraws() throws { + let vectors: [(UInt64, [UInt64])] = [ + (0, [0xe220a8397b1dcdaf, 0x6e789e6aa1b965f4, 0x06c45d188009454f, 0xf88bb8a8724c81ec]), + (1, [0x910a2dec89025cc1, 0xbeeb8da1658eec67, 0xf893a2eefb32555e, 0x71c18690ee42c90b]), + (42, [0xbdd732262feb6e95, 0x28efe333b266f103, 0x47526757130f9f52, 0x581ce1ff0e4ae394]), + (UInt64.max, [0xe4d971771b652c20, 0xe99ff867dbf682c9, 0x382ff84cb27281e9, 0x6d1db36ccba982d2])] + for (seed, expected) in vectors { + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + var rng = SchemaExampleRandom(seed: seed) + for golden in expected { #expect(try rng.next(meter: &meter) == golden) } + #expect(rng.draws == 4 && meter.used(.workUnits) == 40) + } +} + +@Test func g2UniformSelectionsStayInRangeAtUInt64Boundaries() throws { + for bound in [UInt64(1), 2, 7, 128, UInt64.max] { + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + var trace = try testTrace(meter: &meter), rng = SchemaExampleRandom(seed: 42) + for _ in 0..<128 { #expect(try rng.uniform(upperBound: bound, trace: &trace, meter: &meter) < bound) } + #expect(trace.events == rng.draws && rng.draws >= 128) + } +} + +@Test func g2RejectedUniformDrawsChargeWorkAndTraceBeforeRetry() throws { + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + var trace = try testTrace(meter: &meter), rng = SchemaExampleRandom(seed: 3) + let before = meter.used(.workUnits), diagnosticBefore = meter.used(.diagnosticBytes) + let selected = try rng.uniform(upperBound: 0x8000000000000001, trace: &trace, meter: &meter) + #expect(selected == 0x33466f8a7b81a988 && rng.draws == 2 && trace.events == 2) + #expect(meter.used(.workUnits) - before == 3 + 2 * 29) + #expect(meter.used(.diagnosticBytes) - diagnosticBefore == 34) +} + +@Test func g2UniformZeroBoundFailsAndLatchesTheLedger() throws { + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + var trace = try testTrace(meter: &meter), rng = SchemaExampleRandom(seed: 3) + let failure = JSONFailure(.configuration, code: .invalidOperation) + #expect(throws: failure) { try rng.uniform(upperBound: 0, trace: &trace, meter: &meter) } + #expect(throws: failure) { try rng.next(meter: &meter) } + #expect(rng.draws == 0) +} + +@Test func g2WorkExhaustionAfterRejectedDrawCannotResetOrAdvanceRandomState() throws { + var measure = try JSONWorkMeter(limits: .balanced, domain: .core) + let measuredTrace = try testTrace(meter: &measure) + _ = consume measuredTrace + var limits = JSONLimits.balanced; limits[.workUnits] = measure.used(.workUnits) + 3 + 29 + 9 + var meter = try JSONWorkMeter(limits: limits, domain: .core) + var trace = try testTrace(meter: &meter), rng = SchemaExampleRandom(seed: 3) + do { _ = try rng.uniform(upperBound: 0x8000000000000001, trace: &trace, meter: &meter); Issue.record("Rejected-draw work cap ignored") } + catch { #expect(resource(error, .workUnits)) } + #expect(rng.draws == 1 && trace.events == 1) + do { _ = try rng.next(meter: &meter); Issue.record("Failed RNG ledger resumed") } + catch { #expect(resource(error, .workUnits)) } + #expect(rng.draws == 1) +} + +@Test func g2CancellationDuringRandomSearchIsTerminal() async throws { + let task = Task { + var limits = JSONLimits.balanced; limits[.cancellationQuantum] = 1 + var meter = try JSONWorkMeter(limits: limits, domain: .core) + var trace = try testTrace(meter: &meter), rng = SchemaExampleRandom(seed: 3) + let trigger = meter.used(.workUnits) + 3 + 5 + meter.checkpointObserver = { work in if work >= trigger { withUnsafeCurrentTask { $0?.cancel() } } } + do { _ = try rng.uniform(upperBound: 0x8000000000000001, trace: &trace, meter: &meter); Issue.record("Cancellation ignored") } + catch is CancellationError { } + #expect(throws: CancellationError.self) { try rng.next(meter: &meter) } + #expect(rng.draws == 0) + } + try await task.value +} + +@Test func g2ConstructionNormalizesOrderAndLexemesWithoutLosingExactValues() throws { + let a = try prepared(#"{"const":{"z":[[9007199254740993]],"é":1.0,"e\u0301":9007199254740994.5}}"#).generate(seed: 42) + let b = try prepared(#"{"const":{"e\u0301":9007199254740994.5,"é":1,"z":[[9007199254740993]]}}"#).generate(seed: 42) + let text = try JSONWriter().writeString(a.value) + #expect(text.utf8.elementsEqual(#"{"é":90071992547409945e-1,"z":[[9007199254740993]],"é":1}"#.utf8)) + #expect(try JSONWriter().write(b.value) == JSONWriter().write(a.value)) + #expect(a.sourceSnapshotDigest == b.sourceSnapshotDigest && a.traceDigest == b.traceDigest) + #expect(a.value.objectValue?.count == 3 && a.validation.isValid && b.validation.isValid) +} + +@Test func g2ConstructionUsesIterativeCodecForDeepNesting() throws { + var value: JSON = .number(try JSONNumber(parsing: "9007199254740993")) + for _ in 0..<100 { value = .array([value]) } + let document = try JSONSchemaDocument(.object(JSONObject(members: [.init("const", value: value)]))) + let result = try JSONSchemaExampleGenerator(document: document).generate() + #expect(result.value == value && result.validation.isValid) +} + +@Test func g2ConstructionChecksDepthAndNumericCapsBeforeCertification() throws { + var limits = JSONLimits.balanced; limits[.maxDepth] = 2 + do { _ = try prepared(#"{"const":[[[0]]]}"#, options: .init(limits: limits)).generate(); Issue.record("Depth cap ignored") } + catch { #expect(resource(error, .maxDepth)) } + limits = .balanced; limits[.numberDigits] = 3 + do { _ = try prepared(#"{"const":9007199254740993}"#, options: .init(limits: limits)).generate(); Issue.record("Numeric cap ignored") } + catch { #expect(resource(error, .numberDigits)) } +} + +@Test func g2CorpusIsAdmittedExactOrderedAndImmutable() throws { + let syntheticText = #"[null,false,0,"example",[],{}]"# + for _ in 0..<32 { + let corpus = JSONSchemaExampleCorpus.synthetic + #expect(try JSONWriter().writeString(.array(corpus.values)) == syntheticText) + var local = corpus.values + local.append(.string("caller-only")) + #expect(local.count == 7 && corpus.values.count == 6) + #expect(JSONSchemaExampleOptions().corpus.values.count == 6) + } + let one = try JSONParser().parse(#"{"é":1.0,"e\u0301":9007199254740993}"#) + let two = try JSONParser().parse(#"{"e\u0301":9007199254740993,"é":1}"#) + let a = try prepared("true", options: .init(corpus: .init(values: [one, .null]))) + let b = try prepared("true", options: .init(corpus: .init(values: [two, .null]))) + let reversed = try prepared("true", options: .init(corpus: .init(values: [.null, two]))) + #expect(a.corpusDigest == b.corpusDigest && a.corpusDigest != reversed.corpusDigest) + let result = try a.generate(seed: 2, profile: .demonstration) + #expect(result.value == one && result.value.objectValue?.count == 2 && result.validation.isValid) + #expect(result.corpusDigest == a.corpusDigest) +} + +@Test func g2EmptyOrOversizedCorpusFailsDuringPreparation() throws { + #expect(throws: JSONSchemaExampleFailure.invalidConfiguration(.emptyCorpus)) { + try prepared("true", options: .init(corpus: .init(values: []))) + } + var limits = JSONLimits.balanced; limits[.stringBytes] = 4096 + #expect(try prepared("true", options: .init(corpus: .init(values: [.null]), compilationOptions: .init(limits: limits))).generate().validation.isValid) + do { _ = try prepared("true", options: .init(corpus: .init(values: [.string(String(repeating: "x", count: 4097))]), compilationOptions: .init(limits: limits))); Issue.record("Corpus bypassed admission") } + catch { #expect(resource(error, .stringBytes)) } +} + +@Test func g2SnapshotCorpusAndPolicyMatchIndependentTransportGoldens() throws { + let g = try prepared("true") + #expect(g.sourceSnapshotDigest == "a69f2db52526c6c09592bc65478664deb370ef9d834aff678fa3f9879ae555aa") + #expect(g.corpusDigest == "c8cfba87987771a2ae30e1e9d9f345ea45cb7a1e93eb69a5e2a7643881bf6847") + #expect(g.policyDigest == "f9440e6af33f0e80bea2ae4dad4e532259bf722044d4ad0d925ae14d87ed3ff3") +} + +@Test func g2All49GenerationCapsContributeToPolicyIdentity() throws { + var meter = try JSONWorkMeter(limits: .offline, domain: .core) + let base = try SchemaExampleDigests.policy(.init(), meter: &meter) + var count = 0 + for metric in JSONResourceMetric.allCases { + var options = JSONSchemaExampleOptions(); options.limits[metric] -= 1 + #expect(try SchemaExampleDigests.policy(options, meter: &meter) != base) + count += 1 + } + #expect(count == 49) +} + +@Test func g2AllPolicyFlagsAndCompilationEvaluationCapsHaveDistinctIdentity() throws { + var meter = try JSONWorkMeter(limits: .offline, domain: .core) + let base = try SchemaExampleDigests.policy(.init(), meter: &meter) + for slot in 0..<11 { + var o = JSONSchemaExampleOptions() + switch slot { + case 0: o.maximumAttempts = 17 + case 1: o.compilationOptions.formatAssertion = true + case 2: o.compilationOptions.output = .flag + case 3: o.compilationOptions.diagnosticPolicy = .bestEffort + case 4: o.compilationOptions.collectAnnotations = true + case 5: o.validationOptions.formatAssertion = true + case 6: o.validationOptions.output = .flag + case 7: o.validationOptions.diagnosticPolicy = .bestEffort + case 8: o.validationOptions.collectAnnotations = true + case 9: o.compilationOptions.limits[.workUnits] -= 1 + default: o.validationOptions.limits[.workUnits] -= 1 + } + #expect(try SchemaExampleDigests.policy(o, meter: &meter) != base) + } +} + +@Test func g2CompleteTraceGoldensBindSeedProfileSourceCorpusAndPolicy() throws { + let g = try prepared("true"), minimal = try g.generate(seed: 42) + let demo = try g.generate(seed: 42, profile: .demonstration) + #expect(minimal.traceDigest == "b2fecfe6b1cbeeab34f0513708c31e19aa8e61ddf25d603447572b22d4fdd2f1") + #expect(demo.traceDigest == "21735803f7ea97cf7b18d6ab9b23c32fbc7b06ddc200878ae1ddb161bc5525c5") + #expect(minimal.randomDraws == 0 && minimal.traceEventCount == 4) + #expect(demo.randomDraws == 1 && demo.traceEventCount == 5 && demo.value == .boolean(false)) + #expect(minimal.schemaDigestAlgorithm == "swift-json-schema-snapshot-v1") + #expect(demo.rngAlgorithm == "splitmix64-v1" && demo.traceDigestAlgorithm == "swift-json-example-trace-v2") + #expect(try g.generate(seed: 43).traceDigest != minimal.traceDigest) + #expect(try prepared("{}").generate(seed: 42).traceDigest != minimal.traceDigest) + #expect(try prepared("true", options: .init(maximumAttempts: 17)).generate(seed: 42).traceDigest != minimal.traceDigest) + #expect(try prepared("true", options: .init(corpus: .init(values: [.null]))).generate(seed: 42).traceDigest != minimal.traceDigest) +} + +@Test func g2TraceBytesHaveExactFiniteBoundaryWithoutTruncation() throws { + var limits = JSONLimits.balanced; limits[.diagnosticBytes] = 344 + let exact = try prepared("true", options: .init(limits: limits)).generate(seed: 42) + #expect(exact.statistics[.diagnosticBytes] == 344 && exact.traceEventCount == 4) + limits[.diagnosticBytes] = 343 + do { _ = try prepared("true", options: .init(limits: limits)).generate(seed: 42); Issue.record("Truncated trace returned success") } + catch { #expect(resource(error, .diagnosticBytes)) } +} + +@Test func g2DemonstrationCircularScanKeepsRejectedNativeVerdictAndAttemptCeiling() throws { + let source = #"{"enum":[1,"ok"],"type":"string"}"# + let result = try prepared(source).generate(seed: 2, profile: .demonstration) + #expect(result.value == .string("ok") && result.attempts == 2 && result.randomDraws == 1 && result.traceEventCount == 7) + #expect(result.statistics[.evaluationWorkUnits] > 0) + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 1)) { + try prepared(source, options: .init(maximumAttempts: 1)).generate(seed: 2, profile: .demonstration) + } +} + +@Test func g2KeywordDiscoveryIsIndependentOfObjectInsertionOrder() throws { + let a = try prepared(#"{"examples":["last"],"enum":["enum","default"],"default":"default","type":"string"}"#).generate() + let b = try prepared(#"{"type":"string","default":"default","enum":["enum","default"],"examples":["last"]}"#).generate() + #expect(a.value == .string("default") && a.attempts == 1) + #expect(a.sourceSnapshotDigest == b.sourceSnapshotDigest && a.traceDigest == b.traceDigest) +} + +@Test func g2TraceFinalizationIsSingleUse() throws { + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + var trace = try testTrace(meter: &meter) + try trace.event(4, 1, 1, meter: &meter) + #expect(try trace.digest(meter: &meter).count == 64) + let failure = JSONFailure(.configuration, code: .invalidOperation) + #expect(throws: failure) { try trace.event(5, 1, 0, meter: &meter) } + #expect(throws: failure) { try trace.digest(meter: &meter) } +} + +@Test func g2CancellationDuringExactConstructionCannotReturnACandidate() async throws { + let value: JSON = .array((0..<200).map { .string("token-\($0)") }) + let task = Task { + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + meter.checkpointObserver = { work in if work >= 100 { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { try SchemaExampleExact.construct(value, meter: &meter) } + #expect(throws: CancellationError.self) { try meter.work() } + } + try await task.value +} + +@Test func g2StaticReferenceKindsUseStableScalarOrderWithOriginalRootValidation() throws { + let a = try prepared(##"{"$ref":"#/$defs/a","$dynamicRef":"#/$defs/b","$defs":{"a":{"enum":[1,"ok"]},"b":{"const":"ok"}}}"##).generate(seed: 42) + let b = try prepared(##"{"$defs":{"b":{"const":"ok"},"a":{"enum":[1,"ok"]}},"$dynamicRef":"#/$defs/b","$ref":"#/$defs/a"}"##).generate(seed: 42) + #expect(a.value == .string("ok") && a.attempts == 1 && a.validation.isValid) + #expect(a.sourceSnapshotDigest == b.sourceSnapshotDigest && a.traceDigest == b.traceDigest) +} diff --git a/Tests/JSONSchemaExamplesTests/LifecycleAndAccountingTests.swift b/Tests/JSONSchemaExamplesTests/LifecycleAndAccountingTests.swift new file mode 100644 index 0000000..8286f6b --- /dev/null +++ b/Tests/JSONSchemaExamplesTests/LifecycleAndAccountingTests.swift @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema +@testable import JSONSchemaExamples +import Testing + +private func lifecycleGenerator(_ source: String, options: JSONSchemaExampleOptions = .init()) throws -> JSONSchemaExampleGenerator { + try .init(document: JSONSchemaDocument(JSONParser().parse(source)), options: options) +} +private func lifecycleMeter(_ options: JSONSchemaExampleOptions = .init()) throws -> JSONWorkMeter { + try .init(limits: options.limits.intersection(options.validationOptions.limits), domain: .core) +} +private func expectResource(_ error: any Error, metric: JSONResourceMetric, code: JSONFailure.Code = .limitExceeded) { + guard let failure = error as? JSONFailure else { Issue.record("Expected a native resource failure: \(error)"); return } + #expect(failure.category == .resource && failure.code == code && failure.resource?.metric == metric) +} + +@Test func g5TraceFinalizationReleasesOnlyItsActualTransportReservation() throws { + var meter = try lifecycleMeter() + try meter.retain(91) + var trace = try SchemaExampleTrace(snapshot: "s", corpus: "c", policy: "p", seed: 0, profile: .minimal, meter: &meter) + try trace.event(4, 1, 1, meter: &meter) + #expect(meter.used(.retainedBytes) == 91 + trace.retainedByteCount) + #expect(try trace.digest(meter: &meter).count == 64) + #expect(trace.retainedByteCount == 0 && meter.used(.retainedBytes) == 91 + 64) + // Misuse is terminal even when caught; unrelated work cannot resume. + let failure = JSONFailure(.configuration, code: .invalidOperation) + #expect(throws: failure) { try trace.event(1, 0, 0, meter: &meter) } + #expect(throws: failure) { try meter.work() } +} + +@Test func g5SuccessTransfersResultsAndReleasesInvocationReservations() throws { + var options = JSONSchemaExampleOptions(); options.validationOptions.collectAnnotations = true + let prepared = try lifecycleGenerator(#"{"type":"object","required":["a"],"properties":{"a":{"type":"integer","default":7}}}"#, options: options) + var meter = try lifecycleMeter(options) + try meter.retain(73); try meter.work(19) + let result = try prepared.generate(meter: &meter) + #expect(result.validation.isValid && result.value.objectValue?.count == 1) + #expect(result.statistics[.workUnits] == meter.used(.workUnits)) + #expect(result.statistics[.retainedBytes] > 73) + #expect(meter.used(.retainedBytes) == 73 && meter.used(.annotationBytes) == 0 && meter.used(.regexRetainedBytes) == 0) + try meter.work() + #expect(meter.used(.workUnits) == result.statistics[.workUnits] + 1) +} + +@Test func g5RepeatedSuccessDoesNotAccumulateLiveInvocationStateOrResetWork() throws { + let prepared = try lifecycleGenerator(#"{"enum":[0,"ok"],"type":"string"}"#) + let expected = try prepared.generate() + var meter = try lifecycleMeter() + for index: UInt64 in 1...12 { + let result = try prepared.generate(meter: &meter) + #expect(result.value == expected.value && result.traceDigest == expected.traceDigest) + #expect(meter.used(.retainedBytes) == 0 && meter.used(.annotationBytes) == 0) + #expect(meter.used(.workUnits) == expected.statistics[.workUnits] * index) + #expect(meter.used(.evaluationWorkUnits) == expected.statistics[.evaluationWorkUnits] * index) + } +} + +@Test func g5ReturnedCertificateOutlivesPreparedSourceAndRegistryScope() throws { + func scoped() throws -> JSONSchemaExampleResult { + let root = try JSONSchemaDocument(JSONParser().parse(##"{"$ref":"https://e.test/value"}"##)) + let external = try JSONSchemaDocument(JSONParser().parse(#"{"const":{"é":9007199254740993,"e\u0301":null}}"#), retrievalURI: JSONSchemaURI("https://e.test/value")) + return try JSONSchemaExampleGenerator(document: root, registry: .init(documents: [external])).generate() + } + let result = try scoped() + #expect(result.validation.isValid && result.value.objectValue?.count == 2) + #expect(try JSONParser().parse(JSONWriter().write(result.value)) == result.value) + let large = try JSONNumber(parsing: "9007199254740993") + #expect(result.value.objectValue?["é"]?.numberValue == large) +} + +@Test func g5InvalidVerdictsConsumeDiagnosticsAndAllCompletedEvaluationWork() throws { + let source = #"{"enum":[1,2,"ok"],"type":"string"}"# + let prepared = try lifecycleGenerator(source) + let result = try prepared.generate() + let plan = try JSONSchemaCompiler().compile(prepared.document) + var evaluation: UInt64 = 0, issues: UInt64 = 0, diagnostics: UInt64 = 0 + // Compare the same normalized parsed representation the generator certifies; + // native integer construction does not retain the parser's number lexeme. + for value in [try JSONParser().parse("1"), try JSONParser().parse("2"), .string("ok")] { + let report = try JSONSchemaValidator().validateWithReport(value, against: plan) + evaluation += report.statistics[.evaluationWorkUnits] + issues += report.statistics[.issues]; diagnostics += report.statistics[.diagnosticBytes] + } + #expect(result.attempts == 3 && result.statistics[.evaluationWorkUnits] == evaluation) + #expect(result.statistics[.issues] == issues && issues > 0) + #expect(result.statistics[.diagnosticBytes] > diagnostics) // Includes complete abstract trace. +} + +@Test func g5ExactCumulativeQuotaAndRetentionBoundaries() throws { + let source = #"{"default":0,"type":"string","pattern":"^[a-z]{3}$"}"# + let measured = try lifecycleGenerator(source).generate() + for metric: JSONResourceMetric in [.workUnits, .numericWorkUnits, .evaluationWorkUnits, .aggregateRegexWorkUnits, .diagnosticBytes, .issues, .retainedBytes] { + var options = JSONSchemaExampleOptions() + let cap = metric == .retainedBytes ? measured.statistics.peakLogicalRetainedBytes : measured.statistics[metric] + #expect(cap > 0) + options.limits[metric] = cap + let exact = try lifecycleGenerator(source, options: options).generate() + #expect(exact.validation.isValid) + options.limits[metric] = cap - 1 + var meter = try lifecycleMeter(options) + do { _ = try lifecycleGenerator(source, options: options).generate(meter: &meter); Issue.record("One-unit-short \(metric) returned a certificate") } + catch { expectResource(error, metric: metric) } + #expect(meter.used(.retainedBytes) == 0 && meter.used(.annotationBytes) == 0 && meter.used(.regexRetainedBytes) == 0) + let used = meter.statistics() + do { try meter.work(); Issue.record("Exhausted ledger resumed") } catch { expectResource(error, metric: metric) } + #expect(meter.statistics() == used) + } +} + +@Test func g5OperationalEvaluationFailureIsLatchedWithoutRetryOrReport() throws { + var options = JSONSchemaExampleOptions(); options.validationOptions.limits[.evaluationWorkUnits] = 0 + let prepared = try lifecycleGenerator(#"{"enum":[1,2,3]}"#, options: options) + var meter = try lifecycleMeter(options) + do { _ = try prepared.generate(meter: &meter); Issue.record("Operational failure returned success") } + catch { expectResource(error, metric: .evaluationWorkUnits) } + #expect(meter.domain == .schemaEvaluation && meter.used(.evaluationWorkUnits) == 0) + #expect(meter.used(.retainedBytes) == 0) + let stopped = meter.statistics() + do { _ = try prepared.generate(meter: &meter); Issue.record("Invocation restarted after error") } + catch { expectResource(error, metric: .evaluationWorkUnits) } + #expect(meter.statistics() == stopped) +} + +@Test func g5NativeNonproductiveReferenceFailureIsTerminalRatherThanSearchExhaustion() throws { + let prepared = try lifecycleGenerator(##"{"$ref":"#","default":0}"##) + var meter = try lifecycleMeter() + let expected = JSONSchemaEvaluationFailure(.nonproductiveReference) + #expect(throws: expected) { try prepared.generate(meter: &meter) } + #expect(meter.used(.retainedBytes) == 0 && meter.used(.annotationBytes) == 0) + let stopped = meter.statistics() + #expect(throws: expected) { try prepared.generate(meter: &meter) } + #expect(meter.statistics() == stopped) +} + +@Test func g5ExhaustedBranchesReleaseScratchWithoutClaimingUnsatisfiability() throws { + var options = JSONSchemaExampleOptions(maximumAttempts: 3) + options.validationOptions.collectAnnotations = true + let prepared = try lifecycleGenerator(#"{"type":"array","minItems":2,"maxItems":2,"uniqueItems":true,"items":{"enum":[0]}}"#, options: options) + var meter = try lifecycleMeter(options) + let expected = JSONSchemaExampleFailure.searchExhausted(attempts: 3) + #expect(throws: expected) { try prepared.generate(meter: &meter) } + #expect(meter.used(.evaluationWorkUnits) > 0 && meter.used(.issues) >= 3) + #expect(meter.used(.retainedBytes) == 0 && meter.used(.annotationBytes) == 0) + let stopped = meter.statistics() + #expect(throws: expected) { try prepared.generate(meter: &meter) } + #expect(meter.statistics() == stopped) +} + +@Test func g5NestedCyclicConstructionAndProofFailuresReleaseAndLatch() throws { + let cases: [(String, JSONSchemaExampleFailure)] = [ + (##"{"type":"object","required":["a"],"properties":{"a":{"$ref":"#/$defs/loop"}},"$defs":{"loop":{"allOf":[{"$ref":"#/$defs/loop"}]}}}"##, .unsupportedFeature(.cyclicReference)), + ("false", .provenUnsatisfiable(.falseSchema)), + (#"{"not":true}"#, .provenUnsatisfiable(.notTrue))] + for (source, expected) in cases { + let prepared = try lifecycleGenerator(source) + var meter = try lifecycleMeter() + #expect(throws: expected) { try prepared.generate(meter: &meter) } + #expect(meter.used(.retainedBytes) == 0 && meter.used(.regexRetainedBytes) == 0) + let stopped = meter.statistics() + #expect(throws: expected) { try meter.work() } + #expect(meter.statistics() == stopped) + } +} + +@Test func g5OverflowAndRetiredAdmissionCannotWrapOrRefundWork() throws { + var meter = try lifecycleMeter() + try meter.work(7) + do { _ = try meter.adding(.workUnits, UInt64.max, 1); Issue.record("Overflow admitted") } + catch { expectResource(error, metric: .workUnits, code: .counterOverflow) } + #expect(meter.used(.workUnits) == 7) + do { try meter.resetValueBounds(); try meter.work(); Issue.record("Overflow ledger resumed") } + catch { expectResource(error, metric: .workUnits, code: .counterOverflow) } + var retired = try lifecycleMeter() + try retired.work(11); try retired.charge(.nodes, 3); try retired.resetValueBounds() + try retired.charge(.nodes, 2) + #expect(retired.used(.workUnits) == 11 && retired.used(.nodes) == 2 && retired.statistics()[.nodes] == 5) +} + +@Test func g5CancellationAcrossDiscoveryConstructionEvaluationAndTraceIsTerminal() async throws { + let source = #"{"default":0,"type":"string","pattern":"^(a|aa)+b$","minLength":80,"maxLength":80}"# + var options = JSONSchemaExampleOptions(maximumAttempts: 2) + options.limits[.cancellationQuantum] = 1 + let prepared = try lifecycleGenerator(source, options: options) + // Each threshold is charged work, independent of clocks/scheduling. This + // reaches discovery, construction and native regex, then checks terminal state. + var observedRegexCancellation = false + for trigger: UInt64 in [1, 300, 900, 1800, 3000, 5000, 10000, 20000] { + let regex = try await Task { @Sendable [options, prepared, trigger] in + var meter = try lifecycleMeter(options) + meter.checkpointObserver = { work in if work >= trigger { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { try prepared.generate(meter: &meter) } + #expect(meter.used(.retainedBytes) == 0 && meter.used(.annotationBytes) == 0 && meter.used(.regexRetainedBytes) == 0) + let stopped = meter.statistics() + #expect(throws: CancellationError.self) { try prepared.generate(meter: &meter) } + #expect(meter.statistics() == stopped) + return meter.domain == .schemaEvaluation && meter.used(.aggregateRegexWorkUnits) > 0 + }.value + observedRegexCancellation = observedRegexCancellation || regex + } + #expect(observedRegexCancellation) +} + +@Test func g5CancellationInsideLongDecimalDivisionLatchesItsSharedLedger() async throws { + let bound = try JSONNumber(parsing: String(repeating: "9", count: 128)) + let step = try JSONNumber(parsing: "7") + try await Task { + var options = JSONSchemaExampleOptions(); options.limits[.cancellationQuantum] = 1 + var meter = try lifecycleMeter(options) + meter.checkpointObserver = { work in if work >= 500 { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { try SchemaExampleDecimal.rounded(bound, step: step, up: true, open: true, variation: 1, meter: &meter) } + #expect(meter.used(.numericWorkUnits) >= 500) + let stopped = meter.statistics() + #expect(throws: CancellationError.self) { try meter.work() } + #expect(meter.statistics() == stopped) + }.value +} + +@Test func g5CancellationDuringFinalTraceHashNeverPublishesACertificate() async throws { + let prepared = try lifecycleGenerator("true") + let measured = try prepared.generate() + let trigger = measured.statistics[.workUnits] - 1000 + try await Task { + var meter = try lifecycleMeter() + meter.checkpointObserver = { work in if work >= trigger { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { try prepared.generate(meter: &meter) } + #expect(meter.domain == .core && meter.used(.evaluationWorkUnits) == measured.statistics[.evaluationWorkUnits]) + #expect(meter.used(.retainedBytes) == 0) + #expect(throws: CancellationError.self) { try meter.checkpoint() } + }.value + // Separate calls are independent; cancellation never mutates preparation. + #expect(try prepared.generate().traceDigest == measured.traceDigest) +} + +@Test func g5ZeroWorkAndPreparationQuotaFailureDoNotReturnPartialObjects() throws { + var options = JSONSchemaExampleOptions(); options.limits[.workUnits] = 0 + var meter = try lifecycleMeter(options) + do { _ = try lifecycleGenerator("true", options: options).generate(meter: &meter); Issue.record("Zero-work success") } + catch { expectResource(error, metric: .workUnits) } + #expect(meter.used(.workUnits) == 0 && meter.used(.retainedBytes) == 0) + options = .init() + let measured = try lifecycleGenerator("true").preparationStatistics[.workUnits] + options.compilationOptions.limits[.workUnits] = measured + #expect(try lifecycleGenerator("true", options: options).preparationStatistics[.workUnits] == measured) + options.compilationOptions.limits[.workUnits] -= 1 + do { _ = try lifecycleGenerator("true", options: options); Issue.record("Preparation quota reset for snapshot/corpus/policy") } + catch { expectResource(error, metric: .workUnits) } +} + +@Test func g5SharedPreparationSeparatesConcurrentFailureAndSuccessHealth() async throws { + let prepared = try lifecycleGenerator(#"{"type":"integer","minimum":9007199254740993}"#) + let expected = try prepared.generate() + try await withThrowingTaskGroup(of: Bool.self) { group in + for index in 0..<32 { + group.addTask { + if index.isMultiple(of: 2) { + withUnsafeCurrentTask { $0?.cancel() } + do { _ = try prepared.generate(); return false } catch is CancellationError { return true } + } + let result = try prepared.generate() + return result.value == expected.value && result.statistics == expected.statistics && result.traceDigest == expected.traceDigest + } + } + var count = 0 + for try await passed in group { #expect(passed); count += 1 } + #expect(count == 32) + } + #expect(try prepared.generate().value == expected.value) +} + +@Test func g5PolicyDigestReleasesAllTemporaryArrayReservations() throws { + var meter = try lifecycleMeter() + try meter.retain(41) + #expect(try SchemaExampleDigests.policy(.init(), meter: &meter).count == 64) + #expect(meter.used(.retainedBytes) == 41 + 64) + #expect(meter.used(.workUnits) > 0 && meter.used(.compiledPlanBytes) == 0) +} diff --git a/Tests/JSONSchemaExamplesTests/PreparationAndCertificationTests.swift b/Tests/JSONSchemaExamplesTests/PreparationAndCertificationTests.swift new file mode 100644 index 0000000..05c2919 --- /dev/null +++ b/Tests/JSONSchemaExamplesTests/PreparationAndCertificationTests.swift @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: Apache-2.0 +import JSON +import JSONSchema +import JSONSchemaExamples +import Testing + +private func document(_ text: String, uri: String? = nil) throws -> JSONSchemaDocument { + try JSONSchemaDocument(JSONParser().parse(text), retrievalURI: uri.map { try JSONSchemaURI($0) }) +} +private func generator(_ text: String, options: JSONSchemaExampleOptions = .init()) throws -> JSONSchemaExampleGenerator { + try JSONSchemaExampleGenerator(document: document(text), options: options) +} + +@Test func g1TrueAndEmptySchemasReturnNativeCertifiedNull() throws { + for text in ["true", "{}"] { + let prepared = try generator(text) + let result = try prepared.generate(seed: 42) + #expect(result.value.isNull) + #expect(result.validation.isValid && result.validation.diagnosticsComplete) + #expect(result.attempts == 1 && result.seed == 42) + #expect(result.statistics[.evaluationWorkUnits] > 0) + #expect(result.generatorVersion == "s3-candidates-v4") + let native = try JSONSchemaCompiler().compile(prepared.document).validate(result.value) + #expect(native.isValid == result.validation.isValid) + } +} + +@Test func g1ExactConstPreservesLargeDecimalAndScalarKeys() throws { + let prepared = try generator(#"{"const":{"é":9007199254740994.5,"e\u0301":9007199254740993}}"#) + let result = try prepared.generate() + let expected = try JSONParser().parse(#"{"é":9007199254740994.5,"e\u0301":9007199254740993}"#) + #expect(result.value == expected) + #expect(result.value.objectValue?.count == 2) + #expect(result.validation.isValid) +} + +@Test func g1NullConstIsPresentAndG3TypedStringConstructionIsNativeCertified() throws { + #expect(try generator(#"{"const":null}"#).generate().value.isNull) + let typed = try generator(#"{"type":"string"}"#).generate() + #expect(typed.value == .string("") && typed.validation.isValid) +} + +@Test func g1InvalidConstNeverEscapesRootValidation() throws { + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 1)) { + try generator(#"{"const":1,"type":"string"}"#).generate() + } +} + +@Test func g1InvalidEnumAttemptRemainsInCumulativeLedger() throws { + let prepared = try generator(#"{"enum":[1,"ok"],"type":"string"}"#) + let result = try prepared.generate() + #expect(result.value == .string("ok") && result.attempts == 2) + let compiled = try JSONSchemaCompiler().compile(prepared.document) + let finalOnly = try JSONSchemaValidator().validateWithReport(.string("ok"), against: compiled) + #expect(result.statistics[.evaluationWorkUnits] > finalOnly.statistics[.evaluationWorkUnits]) + let again = try prepared.generate() + #expect(again.statistics == result.statistics && again.value == result.value) +} + +@Test func g1AnnotationsAreCandidatesAndNotRepairs() throws { + let result = try generator(#"{"type":"string","default":1,"examples":["ok"]}"#).generate() + #expect(result.value == .string("ok") && result.attempts == 2) + let authored = try generator(#"{"type":"object","required":["id"],"default":{}}"#) + let built = try authored.generate() + #expect(built.value.objectValue?["id"]?.isNull == true && built.attempts == 2) + #expect(authored.document.raw.objectValue?["default"]?.objectValue?.isEmpty == true) +} + +@Test func g1StaticAnchorAndEmbeddedResourceUseCompilerTargets() throws { + let prepared = try generator(##"{"$id":"https://e.test/root","$defs":{"x":{"$id":"nested","$defs":{"v":{"$anchor":"pick","const":9007199254740993}}}},"$ref":"nested#pick"}"##) + let result = try prepared.generate() + #expect(try result.value == JSONParser().parse("9007199254740993")) + #expect(result.validation.isValid) +} + +@Test func g1OfflineRegistryDoesNotGuessFromLastPathComponent() throws { + let a = try document(#"{"const":"first"}"#, uri: "https://a.test/schema") + let b = try document(#"{"const":"second"}"#, uri: "https://b.test/schema") + let registry = try JSONSchemaRegistry(documents: [a,b]) + let prepared = try JSONSchemaExampleGenerator(document: document(#"{"$ref":"https://b.test/schema"}"#), registry: registry) + #expect(try prepared.generate().value == .string("second")) + #expect(prepared.registry.documents.count == 2) +} + +@Test func g1ReferenceSiblingConstraintsRemainAuthoritative() throws { + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 1)) { + try generator(##"{"$defs":{"x":{"const":1}},"$ref":"#/$defs/x","type":"string"}"##).generate() + } +} + +@Test func g1MissingResourceRemainsNativeCompilationFailure() throws { + do { + _ = try generator(#"{"$ref":"https://missing.test/schema"}"#) + Issue.record("Unresolved reference was accepted") + } catch let error as JSONSchemaCompilationFailure { + #expect(error.category == .missingResource) + } +} + +@Test func g1DynamicNavigationAndCyclesFailExplicitly() throws { + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.dynamicReference)) { + try generator(##"{"$dynamicAnchor":"node","$dynamicRef":"#node"}"##).generate() + } + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.cyclicReference)) { + try generator(##"{"$ref":"#"}"##).generate() + } +} + +@Test func g1UnsatisfiableProofsAreRestrictedToSoundRootForms() throws { + #expect(throws: JSONSchemaExampleFailure.provenUnsatisfiable(.falseSchema)) { try generator("false").generate() } + for text in [#"{"not":true}"#, #"{"not":{}}"#] { + #expect(throws: JSONSchemaExampleFailure.provenUnsatisfiable(.notTrue)) { try generator(text).generate() } + } + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction)) { + try generator(#"{"not":{"type":"string"}}"#).generate() + } +} + +@Test func g1InactiveApplicatorCannotProduceNotTrueProof() throws { + let meta = try document(#"{"$id":"https://e.test/core-only","$vocabulary":{"https://json-schema.org/draft/2020-12/vocab/core":true}}"#, uri: "https://e.test/core-only") + let prepared = try JSONSchemaExampleGenerator(document: document(#"{"$schema":"https://e.test/core-only","not":{}}"#), registry: .init(documents: [meta])) + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction)) { try prepared.generate() } + let plan = try JSONSchemaCompiler().compile(prepared.document, registry: prepared.registry) + #expect(try plan.validate(.null).isValid) +} + +@Test func g1AttemptLimitAndGeneratorConfigurationAreExact() throws { + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 1)) { + try generator(#"{"enum":[1,"ok"],"type":"string"}"#, options: .init(maximumAttempts: 1)).generate() + } + for limit in [UInt64(0), UInt64.max] { + #expect(throws: JSONSchemaExampleFailure.invalidConfiguration(.attemptLimit)) { + try generator("true", options: .init(maximumAttempts: limit)) + } + } +} + +@Test func g1PoliciesRemainSeparateAndRequiredFormatAssertionWins() throws { + let options = JSONSchemaExampleOptions(compilationOptions: .init(formatAssertion: true), validationOptions: .init(output: .flag)) + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 1)) { + try generator(#"{"format":"date","const":"not-a-date"}"#, options: options).generate() + } + let meta = try document(#"{"$id":"https://e.test/assertion","$schema":"https://json-schema.org/draft/2020-12/schema","$vocabulary":{"https://json-schema.org/draft/2020-12/vocab/core":true,"https://json-schema.org/draft/2020-12/vocab/meta-data":true,"https://json-schema.org/draft/2020-12/vocab/format-assertion":true}}"#, uri: "https://e.test/assertion") + let required = try JSONSchemaExampleGenerator(document: document(#"{"$schema":"https://e.test/assertion","format":"date","default":"not-a-date"}"#), registry: .init(documents: [meta]), options: .init(maximumAttempts: 1)) + let requiredPlan = try JSONSchemaCompiler().compile(required.document, registry: required.registry) + #expect(try !requiredPlan.validate(.string("not-a-date")).isValid) + #expect(!required.options.validationOptions.formatAssertion) + #expect(throws: JSONSchemaExampleFailure.searchExhausted(attempts: 1)) { try required.generate() } + let result = try generator(#"{"const":"ok","title":"label"}"#, options: .init(validationOptions: .init(output: .flag, collectAnnotations: true))).generate() + #expect(result.validation.outputStyle == .flag && result.validationOptions.collectAnnotations) + #expect(result.validation.annotations.contains { $0.keyword.string == "title" && $0.value == .string("label") }) +} + +@Test func g1OperationalFailureIsNotAnInvalidCandidateRetry() throws { + var limits = JSONLimits.balanced; limits[.evaluationWorkUnits] = 0 + do { + _ = try generator(#"{"enum":[1,2]}"#, options: .init(validationOptions: .init(limits: limits))).generate() + Issue.record("Evaluation quota was ignored") + } catch let error as JSONFailure { + #expect(error.category == .resource) + #expect(error.resource?.metric == .evaluationWorkUnits) + } + var discovery = JSONLimits.balanced; discovery[.schemaNodes] = 0 + do { + _ = try generator(#"{"const":1}"#, options: .init(limits: discovery)).generate() + Issue.record("Declaration discovery quota was ignored") + } catch let error as JSONFailure { + #expect(error.category == .resource && error.resource?.metric == .schemaNodes) + } +} + +@Test func g1SharedWorkBoundaryIncludesEarlierInvalidVerdict() throws { + let source = #"{"enum":[1,"ok"],"type":"string"}"# + let measured = try generator(source).generate() + var limits = JSONLimits.balanced; limits[.workUnits] = measured.statistics[.workUnits] + let exact = try generator(source, options: .init(limits: limits)).generate() + #expect(exact.statistics[.workUnits] == limits[.workUnits]) + limits[.workUnits] -= 1 + do { + _ = try generator(source, options: .init(limits: limits)).generate() + Issue.record("One-unit-short ledger returned a result") + } catch let error as JSONFailure { #expect(error.category == .resource && error.resource?.metric == .workUnits) } +} + +@Test func g1SourceDigestNormalizesKeysAndNumbersButRetainsRegistryOrder() throws { + let a = try generator(#"{"const":{"b":1.0,"a":2}}"#) + let b = try generator(#"{"const":{"a":2,"b":1}}"#) + #expect(a.sourceSnapshotDigest == b.sourceSnapshotDigest && a.sourceSnapshotDigest.count == 64) + #expect(a.preparationStatistics[.compileWorkUnits] > 0) + let x = try document("true", uri: "https://a.test/x"), y = try document("true", uri: "https://b.test/x") + let left = try JSONSchemaExampleGenerator(document: a.document, registry: .init(documents: [x,y])) + let right = try JSONSchemaExampleGenerator(document: a.document, registry: .init(documents: [y,x])) + #expect(left.sourceSnapshotDigest != right.sourceSnapshotDigest) + let distinct = try generator(#"{"const":{"e\u0301":2,"b":1}}"#) + #expect(a.sourceSnapshotDigest != distinct.sourceSnapshotDigest) +} + +@Test func g1QualifiedBridgeHasExactLocationsPoliciesAndCheckedEdges() throws { + let plan = try JSONSchemaCompiler(options: .init(formatAssertion: true)).compile(document(##"{"$id":"https://e.test/root","$defs":{"child":{"$id":"https://e.test/child","const":7}},"$ref":"https://e.test/child"}"##)) + var meter = try JSONWorkMeter(limits: .balanced, domain: .core) + let root = plan.exampleRoot + let ref = try root.reference(at: 0, meter: &meter) + let target = try ref.target.declaration(meter: &meter) + #expect(target.resourceURI.string == "https://e.test/child") + #expect(target.documentPointer.tokens.map(\.string) == ["$defs","child"]) + #expect(target.vocabulary.applies(.const) && target.formatAssertion) + #expect(!ref.requiresDynamicScope && ref.keyword == .ref) + var found = false + for index in 0.. 0) + #expect(throws: JSONFailure(.configuration, code: .invalidOperation)) { try root.reference(at: -1, meter: &meter) } +} + +@Test func g1CancellationTerminatesAnInvocationWithoutPoisoningPreparation() async throws { + let prepared = try generator(#"{"const":42}"#) + let task = Task { + withUnsafeCurrentTask { $0?.cancel() } + return try prepared.generate() + } + do { _ = try await task.value; Issue.record("Cancelled invocation returned a certificate") } + catch is CancellationError { } + #expect(try prepared.generate().validation.isValid) +} + +@Test func g1SharedPreparedGeneratorHasIndependentConcurrentInvocations() async throws { + let prepared = try generator(#"{"enum":[1,"ok"],"type":"string"}"#) + let expected = try prepared.generate() + try await withThrowingTaskGroup(of: JSONSchemaExampleResult.self) { group in + for seed in 0..<16 { group.addTask { try prepared.generate(seed: UInt64(seed)) } } + var count = 0 + for try await result in group { + #expect(result.value == expected.value && result.validation.isValid) + #expect(result.statistics == expected.statistics && result.attempts == 2) + count += 1 + } + #expect(count == 16) + } + let demos = try (0..<16).map { try prepared.generate(seed: UInt64($0), profile: .demonstration) } + try await withThrowingTaskGroup(of: JSONSchemaExampleResult.self) { group in + for seed in 0..<16 { group.addTask { try prepared.generate(seed: UInt64(seed), profile: .demonstration) } } + var count = 0 + for try await result in group { + let expected = demos[Int(result.seed)] + #expect(result.value == expected.value && result.traceDigest == expected.traceDigest) + #expect(result.randomDraws == expected.randomDraws && result.statistics == expected.statistics) + count += 1 + } + #expect(count == 16) + } +} diff --git a/Tests/JSONSchemaExamplesTests/QualificationMatrixTests.swift b/Tests/JSONSchemaExamplesTests/QualificationMatrixTests.swift new file mode 100644 index 0000000..a4b2831 --- /dev/null +++ b/Tests/JSONSchemaExamplesTests/QualificationMatrixTests.swift @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONSchema +import JSONSchemaExamples +import Testing + +private func g7Cases() throws -> [JSON] { + let path = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent() + .appendingPathComponent("Fixtures/SchemaExamples/S3-G7-seeded-v1.json") + let handle = try FileHandle(forReadingFrom: path) + let data = try handle.readToEnd() ?? Data() + try handle.close() + let root = try JSONParser().parse(data) + return try #require(root.objectValue?["cases"]?.arrayValue) +} + +@Test func g7SeededGrammarAlwaysCertifiesRoundtripsAndRejectsKnownMutations() throws { + let cases = try g7Cases() + #expect(cases.count == 64) + var certified = 0, invalid = 0 + for row in cases { + let schema = try #require(row.objectValue?["schema"]) + let document = try JSONSchemaDocument(schema) + let generator = try JSONSchemaExampleGenerator(document: document) + let independent = try JSONSchemaCompiler().compile(document) + #expect(try !independent.validate(#require(row.objectValue?["invalid"])).isValid) + invalid += 1 + for profile in [JSONSchemaExampleProfile.minimal, .demonstration] { + for seed in [UInt64(0), 1, 42, UInt64.max] { + let result = try generator.generate(seed: seed, profile: profile) + #expect(result.validation.isValid && result.validation.diagnosticsComplete) + #expect(try independent.validate(result.value).isValid) + #expect(try JSONParser().parse(JSONWriter().write(result.value)) == result.value) + #expect(result.statistics[.evaluationWorkUnits] > 0) + #expect(result.statistics[.workUnits] <= generator.options.limits[.workUnits]) + #expect(result.attempts > 0 && result.attempts <= generator.options.maximumAttempts) + certified += 1 + } + } + } + #expect(certified == 512 && invalid == 64) + print("S3_G7_MATRIX passed schemas=64 certified=512 invalid=64") +} + +@Test func g7SchemaMemberPermutationPreservesScalarIdentityAndWitnesses() throws { + for i in 0..<8 { + let first = try JSONParser().parse("{\"type\":\"object\",\"required\":[\"é\",\"e\\u0301\"],\"properties\":{\"é\":{\"const\":\(i)},\"e\\u0301\":{\"const\":\(i+1)}}}") + let second = try JSONParser().parse(JSONWriter(options: .init(keyOrder: .scalar)).write(first)) + let a = try JSONSchemaExampleGenerator(document: .init(first)) + let b = try JSONSchemaExampleGenerator(document: .init(second)) + #expect(a.sourceSnapshotDigest == b.sourceSnapshotDigest) + let x = try a.generate(seed: 42), y = try b.generate(seed: 42) + #expect(x.value == y.value && x.traceDigest == y.traceDigest) + #expect(x.value.objectValue?.count == 2) + #expect(x.value.objectValue?["é"] != x.value.objectValue?["e\u{301}"]) + #expect(try JSONSchemaCompiler().compile(a.document).validate(y.value).isValid) + } +} diff --git a/Tests/JSONSchemaExamplesTests/TypedConstructionTests.swift b/Tests/JSONSchemaExamplesTests/TypedConstructionTests.swift new file mode 100644 index 0000000..a43c480 --- /dev/null +++ b/Tests/JSONSchemaExamplesTests/TypedConstructionTests.swift @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONSchema +@testable import JSONSchemaExamples +import Testing + +private func make(_ text: String, options: JSONSchemaExampleOptions = .init()) throws -> JSONSchemaExampleGenerator { + try .init(document: .init(JSONParser().parse(text)), options: options) +} +private let fixtureDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Fixtures/SchemaExamples") +private func fixture(_ name: String) throws -> JSON { try JSONParser().parse(Data(contentsOf: fixtureDirectory.appendingPathComponent(name))) } +private func requireResource(_ error: any Error, _ metric: JSONResourceMetric) { + guard let failure = error as? JSONFailure else { Issue.record("Expected a resource error"); return } + #expect(failure.category == .resource && failure.resource?.metric == metric) +} + +@Test func g3All24RequiredFixturesAreNativeCertifiedForBothProfilesAndFourSeeds() throws { + let data = try fixture("S3-integral-correction-v1.json"), manifest = try fixture("Manifest.json") + let ids = try #require(manifest.objectValue?["g3_must_generate_ids"]?.arrayValue) + let cases = try #require(data.objectValue?["cases"]?.arrayValue) + var observed: [String] = [], generated = 0, witnesses = 0 + for value in cases { + let c = try #require(value.objectValue) + guard c["acceptanceGate"] == .string("G3"), c["generatorExpectation"] == .string("must-generate") else { continue } + let id = try #require(c["id"]?.stringValue), schema = try #require(c["schema"]) + let assertion = c["validation"]?.objectValue?["formatAssertion"]?.booleanValue == true + let options = JSONSchemaExampleOptions(validationOptions: .init(formatAssertion: assertion)) + let generator = try JSONSchemaExampleGenerator(document: .init(schema), options: options) + let independent = try JSONSchemaCompiler(options: options.compilationOptions).compile(generator.document, registry: generator.registry) + let validator = JSONSchemaValidator(options: options.validationOptions) + for witness in try #require(c["witnesses"]?.arrayValue) { + let instance = try #require(witness.objectValue?["instance"]) + let expected = try #require(witness.objectValue?["valid"]?.booleanValue as Bool?) + #expect(try validator.validate(instance, against: independent).isValid == expected, "\(id) witness") + witnesses += 1 + } + for profile in [JSONSchemaExampleProfile.minimal, .demonstration] { + for seed in [UInt64(0), 1, 42, UInt64.max] { + let result = try generator.generate(seed: seed, profile: profile) + #expect(result.validation.isValid && result.validation.diagnosticsComplete, "\(id)") + #expect(try validator.validate(result.value, against: independent).isValid, "\(id)") + #expect(try JSONParser().parse(JSONWriter().write(result.value)) == result.value, "\(id)") + #expect(result.statistics[.workUnits] >= result.statistics[.evaluationWorkUnits]) + #expect(result.statistics[.workUnits] <= options.limits[.workUnits]) + generated += 1 + } + } + observed.append(id) + print("G3_FIXTURE passed id=\(id) profiles=2 seeds=4 generated=8") + } + #expect(observed.map(JSON.string) == ids && observed.count == 24 && generated == 192) + #expect(witnesses > 0) + print("G3_ACCEPTANCE passed cases=\(observed.count) generated=\(generated) witnesses=\(witnesses)") +} + +@Test func g3TwoFixtureProofsRemainSeparateFromSearchFailure() throws { + let cases = try #require(fixture("S3-integral-correction-v1.json").objectValue?["cases"]?.arrayValue) + var count = 0 + for c in cases where c.objectValue?["acceptanceGate"] == .string("G3") && c.objectValue?["generatorExpectation"] == .string("must-prove-unsatisfiable") { + let generator = try JSONSchemaExampleGenerator(document: .init(try #require(c.objectValue?["schema"]))) + let proof: JSONSchemaUnsatisfiableProof = c.objectValue?["id"] == .string("boolean-false") ? .falseSchema : .notTrue + #expect(throws: JSONSchemaExampleFailure.provenUnsatisfiable(proof)) { try generator.generate() } + count += 1 + } + #expect(count == 2) +} + +@Test func g3NestedScalarKeysAndConjunctivePropertiesNeverOverwrite() throws { + let source = #"{"type":"object","allOf":[{"properties":{"é":{"type":"integer","minimum":2},"e\u0301":{"const":9}},"required":["é","e\u0301"]},{"properties":{"é":{"maximum":3}},"required":["é"]}]}"# + let result = try make(source).generate() + #expect(result.value.objectValue?.count == 2) + #expect(result.value.objectValue?["é"] == 2 && result.value.objectValue?["e\u{301}"] == 9) + #expect(result.validation.isValid) + let incompatible = #"{"type":"object","allOf":[{"properties":{"a":{"type":"integer"}},"required":["a"]},{"properties":{"a":{"type":"string"}},"required":["a"]}]}"# + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction)) { try make(incompatible).generate() } +} + +@Test func g3EachConjunctControlsAdditionalPropertiesIndependently() throws { + let source = #"{"type":"object","properties":{"a":{"const":1}},"additionalProperties":false,"allOf":[{"required":["b"],"properties":{"b":{"const":2}}}]}"# + #expect(throws: JSONSchemaExampleFailure.unsupportedFeature(.candidateConstruction)) { try make(source).generate() } +} + +@Test func g3NegativeAndOpenExactNumericIntervalsNeverUseEpsilon() throws { + for (schema, expected) in [ + (#"{"type":"integer","maximum":-1.25}"#, "-2"), + (#"{"type":"integer","exclusiveMinimum":-2,"maximum":-1}"#, "-1"), + (#"{"type":"number","exclusiveMinimum":0.000000000000000000000001,"exclusiveMaximum":0.000000000000000000000002}"#, "11e-25"), + (#"{"type":"number","minimum":-0.049,"maximum":-0.021,"multipleOf":0.01}"#, "-0.04"), + (#"{"type":"integer","minimum":1,"multipleOf":2.5}"#, "5"), + (#"{"type":"integer","minimum":123456789012345678901234567890123456789}"#, "123456789012345678901234567890123456789") + ] { + let result = try make(schema).generate() + #expect(try result.value == JSONParser().parse(expected)) + #expect(result.validation.isValid && result.statistics[.numericWorkUnits] > 0) + } +} + +@Test func g3ScalarLengthUsesDecodedScalarsAndPreservesCorpusSpelling() throws { + let options = JSONSchemaExampleOptions(corpus: .init(values: [.string("e\u{301}")])) + let result = try make(#"{"type":"string","minLength":2,"maxLength":2}"#, options: options).generate(profile: .demonstration) + #expect(result.value == .string("e\u{301}") && result.validation.isValid) +} + +@Test func g3AssertionPolicyRemainsNativeAndContentMetadataDoesNotExecute() throws { + let source = #"{"type":"string","format":"email","contentEncoding":"base64","contentMediaType":"text/html"}"# + #expect(try make(source).generate().value == .string("")) + let asserted = try make(source, options: .init(validationOptions: .init(formatAssertion: true))).generate() + #expect(asserted.value == .string("example@example.com") && asserted.validation.isValid) +} + +@Test func g3MinimumPropertiesAndUniqueArrayElementsHaveCheckedConstruction() throws { + let object = try make(#"{"type":"object","minProperties":3,"maxProperties":3,"additionalProperties":{"type":"integer"}}"#).generate() + #expect(object.value.objectValue?.members.map(\.key.string) == ["p0","p1","p2"]) + let array = try make(#"{"type":"array","minItems":3,"uniqueItems":true,"items":{"type":"integer"}}"#).generate() + #expect(array.value == .array([0,1,2]) && array.validation.isValid) +} + +@Test func g3ConstructedArraysRetainNestedElementsAndDepth() throws { + let source = #"{"type":"array","minItems":2,"items":{"type":"array","minItems":2,"items":{"type":"integer"}}}"# + let result = try make(source).generate() + #expect(result.value == .array([.array([0,0]), .array([0,0])])) +} + +@Test func g3TypedConstructionHasAnExactCumulativeWorkBoundary() throws { + let source = #"{"type":"object","required":["v"],"properties":{"v":{"type":"number","minimum":0.02,"multipleOf":0.01}},"default":{}}"# + let result = try make(source).generate() + #expect(result.attempts == 2 && result.validation.isValid) + var limits = JSONLimits.balanced; limits[.workUnits] = result.statistics[.workUnits] + #expect(try make(source, options: .init(limits: limits)).generate().statistics[.workUnits] == limits[.workUnits]) + limits[.workUnits] -= 1 + do { _ = try make(source, options: .init(limits: limits)).generate(); Issue.record("Cumulative budget reset") } + catch { requireResource(error, .workUnits) } +} + +@Test func g3TypedStagingChecksDepthCardinalityAndNumericWorkBeforeGrowth() throws { + for (schema, metric, cap) in [ + (#"{"type":"array","minItems":3,"items":{"type":"integer"}}"#, JSONResourceMetric.arrayItems, UInt64(2)), + (#"{"type":"object","minProperties":2}"#, .objectMembers, 1), + (#"{"type":"array","minItems":1,"items":{"type":"array"}}"#, .maxDepth, 1), + (#"{"type":"number","minimum":0.02,"multipleOf":0.01}"#, .numericWorkUnits, 0), + (#"{"type":"integer","minimum":9007199254740993}"#, .numberDigits, 3) + ] { + var limits = JSONLimits.balanced; limits[metric] = cap + do { _ = try make(schema, options: .init(limits: limits)).generate(); Issue.record("Staging cap ignored") } + catch { requireResource(error, metric) } + } +} + +@Test func g3DenseAlignmentFailsFinitelyWithoutApproximation() throws { + do { _ = try make(#"{"type":"number","exclusiveMinimum":1e10000,"multipleOf":0.01}"#).generate(); Issue.record("Unbounded dense decimal") } + catch { requireResource(error, .numberDigits) } +} + +@Test func g3OperationalValidationFailureNeverAdvancesTypedSearch() throws { + var limits = JSONLimits.balanced; limits[.evaluationWorkUnits] = 0 + do { _ = try make(#"{"type":"integer"}"#, options: .init(validationOptions: .init(limits: limits))).generate(); Issue.record("Operational error retried") } + catch { requireResource(error, .evaluationWorkUnits) } +} + +@Test func g3CancellationInsideTypedFramesIsTerminal() async throws { + let generator = try make(#"{"type":"object","required":["a"],"properties":{"a":{"type":"integer"}}}"#) + let plan = try JSONSchemaCompiler().compile(generator.document) + let task = Task { + var limits = JSONLimits.balanced; limits[.cancellationQuantum] = 1 + var meter = try JSONWorkMeter(limits: limits, domain: .core) + var random = SchemaExampleRandom(seed: 0) + var trace = try SchemaExampleTrace(snapshot: "s", corpus: "c", policy: "p", seed: 0, profile: .minimal, meter: &meter) + let trigger = meter.used(.workUnits) + 100 + meter.checkpointObserver = { work in if work >= trigger { withUnsafeCurrentTask { $0?.cancel() } } } + #expect(throws: CancellationError.self) { + try SchemaExampleConstructor.construct(nodes: [plan.exampleRoot], variation: 0, profile: .minimal, corpus: [], assertion: false, + random: &random, trace: &trace, meter: &meter) + } + #expect(throws: CancellationError.self) { try meter.work() } + } + try await task.value + #expect(try generator.generate().validation.isValid) +} + +@Test func g3OneHundredRepeatedTypedInvocationsHaveNoStateDrift() throws { + let generator = try make(#"{"type":"object","required":["id","label"],"properties":{"id":{"type":"integer","minimum":9007199254740993},"label":{"type":"string","minLength":2}}}"#) + let expected = try generator.generate(seed: 42, profile: .demonstration) + for _ in 0..<100 { + let result = try generator.generate(seed: 42, profile: .demonstration) + #expect(result.value == expected.value && result.traceDigest == expected.traceDigest && result.statistics == expected.statistics) + } +} + +@Test func g3SharedTypedGeneratorHasIndependentConcurrentInvocations() async throws { + let generator = try make(#"{"type":"array","minItems":3,"uniqueItems":true,"items":{"type":"integer","minimum":9007199254740993}}"#) + let minimal = try (0..<16).map { try generator.generate(seed: UInt64($0)) } + let demonstration = try (0..<16).map { try generator.generate(seed: UInt64($0), profile: .demonstration) } + try await withThrowingTaskGroup(of: JSONSchemaExampleResult.self) { group in + for seed in 0..<16 { + group.addTask { try generator.generate(seed: UInt64(seed)) } + group.addTask { try generator.generate(seed: UInt64(seed), profile: .demonstration) } + } + var count = 0 + for try await result in group { + let expected = result.profile == .minimal ? minimal[Int(result.seed)] : demonstration[Int(result.seed)] + #expect(result.value == expected.value && result.traceDigest == expected.traceDigest && result.statistics == expected.statistics) + count += 1 + } + #expect(count == 32) + } +} + +@Test func g3PrefixIndicesAboveNineRemainPositionalDecimalText() throws { + var members: [JSON] = [] + for index in 0..<12 { members.append(.object(try JSONObject(members: [.init("const", value: .number(JSONNumber(integer: index)))]))) } + let schema = try JSONObject(members: [.init("type", value: .string("array")), .init("minItems", value: 12), .init("prefixItems", value: .array(members)), .init("items", value: false)]) + let result = try JSONSchemaExampleGenerator(document: .init(.object(schema))).generate() + #expect(result.value == .array((0..<12).map { .number(JSONNumber(integer: $0)) })) +} + +@Test func g3SignedRoundingMatchesIndependentIntegerArithmeticGrid() throws { + var meter = try JSONWorkMeter(limits: .offline, domain: .core) + var observed = 0 + for numerator in -17...17 { + for divisor in 1...7 { + for up in [false, true] { + for open in [false, true] { + for variation in [UInt64(0),1,2] { + var quotient = numerator / divisor + let remainder = numerator % divisor + if up && remainder > 0 { quotient += 1 } + if !up && remainder < 0 { quotient -= 1 } + if open && remainder == 0 { quotient += up ? 1 : -1 } + quotient += (up ? 1 : -1) * Int(variation) + let result = try SchemaExampleDecimal.rounded(JSONNumber(integer: numerator), step: JSONNumber(integer: divisor), + up: up, open: open, variation: variation, meter: &meter) + #expect(result == JSONNumber(integer: quotient * divisor)) + observed += 1 + } + } + } + } + } + #expect(observed == 2940 && meter.used(.numericWorkUnits) > 0) +} + +@Test func g3RepeatedStaticTargetsAreDAGSharingAndRecursiveBaseCasesRemainFinite() throws { + let dag = ##"{"$defs":{"x":{"type":"integer"}},"allOf":[{"$ref":"#/$defs/x"},{"$ref":"#/$defs/x"}]}"## + #expect(try make(dag).generate().value == 0) + let recursive = ##"{"$defs":{"x":{"type":"array","items":{"$ref":"#/$defs/x"}}},"$ref":"#/$defs/x"}"## + #expect(try make(recursive).generate().value == .array([])) +} diff --git a/Tests/SchemaExamplesCompilerFixtures/ArrayFrameSendable.swift b/Tests/SchemaExamplesCompilerFixtures/ArrayFrameSendable.swift new file mode 100644 index 0000000..058e8df --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/ArrayFrameSendable.swift @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func send(_ value: borrowing T) {} +func transfer(_ state: borrowing SchemaExampleArrayFrame) { send(state) } diff --git a/Tests/SchemaExamplesCompilerFixtures/FrameSendable.swift b/Tests/SchemaExamplesCompilerFixtures/FrameSendable.swift new file mode 100644 index 0000000..01992d0 --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/FrameSendable.swift @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func send(_ value: borrowing T) {} +func transfer(_ state: borrowing SchemaExampleFrame) { send(state) } diff --git a/Tests/SchemaExamplesCompilerFixtures/MoveAndShare.swift b/Tests/SchemaExamplesCompilerFixtures/MoveAndShare.swift new file mode 100644 index 0000000..6188957 --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/MoveAndShare.swift @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func move(_ state: consuming SchemaExampleRandom) { let moved = state; _ = consume moved } +func share(_ value: T) {} +func sharePrepared(_ value: JSONSchemaExampleGenerator) { share(value) } diff --git a/Tests/SchemaExamplesCompilerFixtures/ObjectFrameSendable.swift b/Tests/SchemaExamplesCompilerFixtures/ObjectFrameSendable.swift new file mode 100644 index 0000000..c5ff5e0 --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/ObjectFrameSendable.swift @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func send(_ value: borrowing T) {} +func transfer(_ state: borrowing SchemaExampleObjectFrame) { send(state) } diff --git a/Tests/SchemaExamplesCompilerFixtures/RandomCopy.swift b/Tests/SchemaExamplesCompilerFixtures/RandomCopy.swift new file mode 100644 index 0000000..959f863 --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/RandomCopy.swift @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func duplicate(_ state: consuming SchemaExampleRandom) { let other = state; _ = consume state; _ = consume other } diff --git a/Tests/SchemaExamplesCompilerFixtures/RandomSendable.swift b/Tests/SchemaExamplesCompilerFixtures/RandomSendable.swift new file mode 100644 index 0000000..b83d143 --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/RandomSendable.swift @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func send(_ value: borrowing T) {} +func transfer(_ state: borrowing SchemaExampleRandom) { send(state) } diff --git a/Tests/SchemaExamplesCompilerFixtures/TraceCopy.swift b/Tests/SchemaExamplesCompilerFixtures/TraceCopy.swift new file mode 100644 index 0000000..06fa370 --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/TraceCopy.swift @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func duplicate(_ state: consuming SchemaExampleTrace) { let other = state; _ = consume state; _ = consume other } diff --git a/Tests/SchemaExamplesCompilerFixtures/TraceSendable.swift b/Tests/SchemaExamplesCompilerFixtures/TraceSendable.swift new file mode 100644 index 0000000..6d7a4aa --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/TraceSendable.swift @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func send(_ value: borrowing T) {} +func transfer(_ state: borrowing SchemaExampleTrace) { send(state) } diff --git a/Tests/SchemaExamplesCompilerFixtures/TransportSendable.swift b/Tests/SchemaExamplesCompilerFixtures/TransportSendable.swift new file mode 100644 index 0000000..b30b9db --- /dev/null +++ b/Tests/SchemaExamplesCompilerFixtures/TransportSendable.swift @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 +@testable import JSONSchemaExamples +func send(_ value: borrowing T) {} +func transfer(_ state: borrowing SchemaExampleTransport) { send(state) } From b77d43d7b2eceae72b2cbbfc7414143d29834563 Mon Sep 17 00:00:00 2001 From: Thorsten Fuchs <76904008+ThorFuchs@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:55:54 +0200 Subject: [PATCH 2/2] Propose read-only public structural snapshots with external proofs --- .../Architecture/ExtractionManifest.json | 3 +- .../SnapshotPromotion.md | 63 ++++++++ .../Qualification/TestDiscovery.json | 10 +- README.md | 9 ++ Scripts/ci.py | 5 +- Scripts/qualify_snapshot.py | 120 ++++++++++++++ Scripts/test_qualification_harness.py | 6 +- Sources/JSONCLIEngine/CLIBuildIdentity.swift | 2 +- .../Documentation.docc/JSONInspection.md | 8 + .../Documentation.docc/StructuralSnapshots.md | 85 ++++++++++ .../JSONStructuralSnapshot.swift | 115 +++++++++---- .../SnapshotPromotionTests.swift | 123 ++++++++++++++ Tools/SnapshotQualification/Package.swift | 12 ++ Tools/SnapshotQualification/README.md | 10 ++ .../Sources/SnapshotConsumer/Main.swift | 151 ++++++++++++++++++ 15 files changed, 684 insertions(+), 38 deletions(-) create mode 100644 Documentation/Architecture/StructuralIntelligence/SnapshotPromotion.md create mode 100644 Scripts/qualify_snapshot.py create mode 100644 Sources/JSONInspection/Documentation.docc/StructuralSnapshots.md create mode 100644 Tests/JSONInspectionTests/SnapshotPromotionTests.swift create mode 100644 Tools/SnapshotQualification/Package.swift create mode 100644 Tools/SnapshotQualification/README.md create mode 100644 Tools/SnapshotQualification/Sources/SnapshotConsumer/Main.swift diff --git a/Documentation/Architecture/ExtractionManifest.json b/Documentation/Architecture/ExtractionManifest.json index c8d5c12..0956116 100644 --- a/Documentation/Architecture/ExtractionManifest.json +++ b/Documentation/Architecture/ExtractionManifest.json @@ -300,7 +300,8 @@ "1.6 S3 G8 materializes the unchanged synthetic corpus per caller access, replacing process-lifetime stored JSON payloads exposed by Linux ASan/LSan. The public process-lifetime consumer and strengthened exact/value-copy test are original Apache-2.0 qualification code; no suppression, runtime fallback, new dependency or corpus/digest change is introduced.", "Composition reference-load fixtures, serial descriptor staging, public-only adapter, capacity/terminal tests and strict CI receipt checks are original Apache-2.0 code. Generated JSON graphs introduce no upstream protocol fixtures, engine or dependency. Core public APIs, finite profiles and accounting weights are unchanged.", "NAV5 public array-evidence packaging, external consumer and DocC are original Apache-2.0 code. Existing exact shape evidence, exclusive reducer, weights and I01-I17 semantics are unchanged; no upstream engine or dependency is adapted. Promotion is a draft candidate, not a released product.", - "NAV5 package-only complete structural snapshot, parent-linked metadata, original source-ownership transfer and S01-S24 synthetic tests are original Apache-2.0 code. The existing read scope admits once; its default public read/traversal/inspection completion behavior is preserved. No persistent identity, upstream algorithm or new dependency is introduced." + "NAV5 package-only complete structural snapshot, parent-linked metadata, original source-ownership transfer and S01-S24 synthetic tests are original Apache-2.0 code. The existing read scope admits once; its default public read/traversal/inspection completion behavior is preserved. No persistent identity, upstream algorithm or new dependency is introduced.", + "NAV6 snapshot access-level promotion, external consumer, independent 28-record maintainer oracle, terminal-transfer/concurrent lookup proofs and compiled DocC are original Apache-2.0 contributions. No upstream engine, new semantic authority, dependency or additional product is adapted." ], "jsonhtml_export_original_contributions": { "license": "Apache-2.0", diff --git a/Documentation/Architecture/StructuralIntelligence/SnapshotPromotion.md b/Documentation/Architecture/StructuralIntelligence/SnapshotPromotion.md new file mode 100644 index 0000000..bf57585 --- /dev/null +++ b/Documentation/Architecture/StructuralIntelligence/SnapshotPromotion.md @@ -0,0 +1,63 @@ +# NAV6 snapshot public candidate + +This isolated draft descends from original package-only N3 872b5fd and reconciled +N2 37d84e8 (current main c79d48c). It proposes a public facade inside the existing +JSONInspection product; it creates no additional SwiftPM product. No merge or +release is authorized. Original #41/#42 evidence identities remain unchanged. + +## Accepted interface + +Keep JSONStructuralSnapshotBuilder(document:limits:), build(maximumRecords:), +JSONStructuralSnapshot.document/recordCount, record(at:), location/value/sourceSpan +(for:limits:), JSONStructuralRecord and the two existing typed failure cases. +There are no public record/snapshot initializers or setters. The implementation +remains the original package/private algorithm; no scope, meter, reducer, observer, +borrowed cursor or source-rebinding capability becomes public. + +An ordinal is an integer position local to the snapshot method being called, +never a detached node identity. Two sources may both have position 1 with different +values; explicitly selecting each snapshot selects its own original source. No +method accepts a record, handle or replacement document. The immutable document +attachment keeps old snapshots coherent after other values are patched/replaced. + +## Ownership and errors + +Build performs complete physical preorder under Navigation’s one original scope +and meter. Source ownership transfers once after finalized half-open subtree ends; +ordinary read/visit/collect/inspectArray never transfer and preserve refunds. All +three counters (record count, open depth, subtree end) are bounded by arrays and +admitted nodes. Parent assignment uses existing indices; end equals records.count; +ordinal < maximumRecords is checked before append, so count can at most Int.max +without an increment from Int.max. No end/count arithmetic adds unchecked user +integers. Core retention/work increments use checked UInt64 accounting. Supported +wasm32 capacities retain the existing admission policy; no new narrowing occurs. + +Negative maximumRecords precedes admission; zero refuses the real root. No quota, +cancellation, incomplete traversal or finalization failure returns an artifact. +The exclusive context latches owned failures, including second transfer. A direct +package read-scope transfer guard is an implementation primitive; public N3 +construction reaches it only through that guarded context. Post-transfer caller +errors and quota/cancellation still invalidate the scope and publish no result. + +## Costs and qualification + +Overlay v1 remains 128 fixed logical bytes, 96 per record, edge-key UTF-8 bytes, +16 per open frame, plus original source/path costs. Compact record retention is +O(nodes + key bytes), while existing walker full-path storage and copying remain +depth-sensitive. record(at:) is O(1) trusted-memory metadata access. Each location, +value or span lookup is an explicitly separate finite operation with full source +re-admission and core prefix/path/selection work. K lookups pay K admissions. These +are semantic costs, not new performance claims; logical bytes are not heap or RSS. + +All I01–I17 and S01–S24 identities remain. Three new proofs add an independent +28-record oracle, post-transfer terminal/lifetime cases and concurrent lookup +health isolation. Exact discovery rises to 747 native / 731 Linux / 643 WASI, +with 325/288 parameter invocations unchanged, 44 Inspection functions and 50/49 +shared-state functions. Preserve 49 cap probes, 126 maintenance identities, +2-positive/20-negative full codegen, composition and schema-example lanes. Public +qualification adds fourteen external cases, three executable DocC examples and +18 external full-codegen negative outcomes. Requirements become passes only when +actual source-bound receipts say so; SDK compilation is not device execution. + +Read the [compiled guide](../../../Sources/JSONInspection/Documentation.docc/StructuralSnapshots.md) +and [independent consumer](../../../Tools/SnapshotQualification). diff --git a/Documentation/Qualification/TestDiscovery.json b/Documentation/Qualification/TestDiscovery.json index 7be3994..65078da 100644 --- a/Documentation/Qualification/TestDiscovery.json +++ b/Documentation/Qualification/TestDiscovery.json @@ -12,7 +12,7 @@ "JSONCLITests": 88, "JSONNavigationTests": 47, "JSONSchemaExamplesTests": 98, - "JSONInspectionTests": 41 + "JSONInspectionTests": 44 }, "parameterized_invocations": 325, "composition_external_functions": 5, @@ -35,7 +35,7 @@ "JSONCLITests": 1, "JSONNavigationTests": 1, "JSONSchemaExamplesTests": 4, - "JSONInspectionTests": 2 + "JSONInspectionTests": 3 }, "coverage_unmapped": { "Sources/JSON/Pointer/JSONLocationComponent.swift": "declaration-only enum", @@ -76,7 +76,7 @@ "JSONCLITests": 88, "JSONNavigationTests": 47, "JSONSchemaExamplesTests": 98, - "JSONInspectionTests": 41 + "JSONInspectionTests": 44 }, "wasi_root_functions": { "JSONTests": 190, @@ -89,7 +89,7 @@ "JSONBridgesTests": 10, "JSONNavigationTests": 47, "JSONSchemaExamplesTests": 98, - "JSONInspectionTests": 41 + "JSONInspectionTests": 44 }, "wasi_parameterized_invocations": 288, "portable_shared_state_functions": { @@ -103,7 +103,7 @@ "JSONCLITests": 1, "JSONNavigationTests": 1, "JSONSchemaExamplesTests": 4, - "JSONInspectionTests": 2 + "JSONInspectionTests": 3 }, "export_functions": { "JSONSchemaTests": 1, diff --git a/README.md b/README.md index 230e6b5..e1d6d52 100644 --- a/README.md +++ b/README.md @@ -654,3 +654,12 @@ Full source admission still includes the unobserved suffix. Complete row evidenc can have restricted descendant traversal; structural rectangular width is not numeric matrix or schema admission. See the [compiled evidence guide](Sources/JSONInspection/Documentation.docc/ArrayEvidence.md), [public consumer](Tools/InspectionQualification) and [accounting contract](Documentation/Architecture/StructuralIntelligence/Inspection.md). + +The isolated NAV6 candidate also proposes complete source-attached structural +snapshots inside JSONInspection. Read-only parent/edge records preserve physical +preorder and finalized subtree ranges; every value/path/span lookup uses the +captured document and an explicitly separate finite source admission. It adds no +snapshot product, persistent identity or table certification. See the +[snapshot guide](Sources/JSONInspection/Documentation.docc/StructuralSnapshots.md) +and [public consumer](Tools/SnapshotQualification). This API remains an unmerged, +unreleased draft candidate; published products retain their tagged contracts. diff --git a/Scripts/ci.py b/Scripts/ci.py index 427aaf6..606dea7 100644 --- a/Scripts/ci.py +++ b/Scripts/ci.py @@ -14,7 +14,7 @@ SHARED_TESTS += '|g1SharedPreparedGeneratorHasIndependentConcurrentInvocations|g3SharedTypedGeneratorHasIndependentConcurrentInvocations|g4SharedAdvancedGeneratorHasIndependentConcurrentInvocations|g5SharedPreparationSeparatesConcurrentFailureAndSuccessHealth' SHARED_TESTS += '|navigationSharedImmutableRootIndependentInvocations' SHARED_TESTS += '|inspectionI17CoverageSpansReplacementAndSharedInvocations' -SHARED_TESTS += '|snapshotS19NavigationParityAndIndependentInvocations' +SHARED_TESTS += '|snapshotS19NavigationParityAndIndependentInvocations|snapshotV22ConcurrentLookupsUseIndependentAdmissionAndHealth' R0_TESTS = 'readerProbe|indexProbes|duplicateRecognitionPrecedesValueAndClosingSyntax' def snapshot(): @@ -166,6 +166,7 @@ def commands(lane, output): ['python3', '-B', 'Scripts/qualify_ergonomics.py'], ['python3', '-B', 'Scripts/qualify_navigation.py', '--output', str(output / 'navigation')], ['python3', '-B', 'Scripts/qualify_inspection.py', '--output', str(output / 'inspection-consumer')], + ['python3', '-B', 'Scripts/qualify_snapshot.py', '--output', str(output / 'snapshot-consumer')], ['python3', '-B', 'Scripts/qualify_navigation_reducer.py', '--output', str(output / 'owned-codegen')], ['swift', 'test', '--package-path', 'Tools/MobileQualification'], ['swift', 'run', '-c', 'release', '--package-path', 'Tools/MobileQualification', 'MobileQualification'], @@ -299,6 +300,8 @@ def check_result(command, code, log, events=None): passed = passed and 'ERGONOMICS_COMPILER passed commands=8 guide_fences=3' in log.splitlines() if command[:3] == ['python3', '-B', 'Scripts/qualify_navigation_reducer.py']: passed = passed and 'REDUCER_CODEGEN passed positive=2 negative=20' in log.splitlines() + if command[:3] == ['python3', '-B', 'Scripts/qualify_snapshot.py']: + passed = passed and 'SNAPSHOT_COMPILER passed configurations=2 consumer_cases=14 address_cases=14 guide_fences=3 negative_probes=18' in log.splitlines() if command[:3] == ['python3', '-B', 'Scripts/qualify_inspection.py']: passed = passed and 'INSPECTION_COMPILER passed configurations=2 consumer_cases=12 address_cases=12 guide_fences=3 negative_probes=8' in log.splitlines() if command[:3] == ['python3', '-B', 'Scripts/qualify_navigation.py']: diff --git a/Scripts/qualify_snapshot.py b/Scripts/qualify_snapshot.py new file mode 100644 index 0000000..903637a --- /dev/null +++ b/Scripts/qualify_snapshot.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Qualify public snapshot imports, semantics and three executable DocC examples.""" +import argparse +import hashlib +import json +import pathlib +import re +import subprocess +import sys +from archive_candidate import files + +ROOT = pathlib.Path(__file__).resolve().parents[1] +CHECKS = {'roots', 'typed-paths', 'unicode', 'numbers', 'duplicate-refusal', + 'preorder-ranges', 'core-differential', 'source-binding', 'bounds', + 'separate-admission', 'capacity-configuration', 'inclusive-budgets', + 'coverage-separation', 'concurrent-lookups'} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', required=True) + output = ROOT / parser.parse_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 = dict(status='running', source_inputs=inputs, commands=[], consumer_results={}) + + def save(): + (output / 'receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') + + def run(name, command, expected=None): + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=900) + text = result.stdout + result.stderr + crash = result.returncode < 0 or any(x in text for x in + ['Stack dump:', 'Assertion failed', 'fatal error encountered during compilation']) + diagnostics = re.findall(r': error: ([^\n]+)', result.stderr) + passed = not crash and (result.returncode == 0 if expected is None else + result.returncode > 0 and any(expected in d for d in diagnostics)) + log = output / (name + '.log') + log.write_text(text.replace(str(ROOT), '.')) + receipt['commands'].append(dict(name=name, command=[x.replace(str(ROOT), '.') for x in command], + exit_code=result.returncode, status='passed' if passed else 'failed', compiler_crash=crash, + expected_diagnostic=expected, primary_diagnostics=diagnostics, + log=log.relative_to(ROOT).as_posix(), log_sha256=hashlib.sha256(log.read_bytes()).hexdigest())) + save() + if not passed: + raise ValueError('Unexpected compiler/consumer outcome: ' + name) + return result.stdout + + def consumer(name, command): + text = run(name, command) + checks = re.findall(r'^SNAPSHOT_CHECK (\S+) passed$', text, re.M) + if len(checks) != 14 or set(checks) != CHECKS or 'SNAPSHOT_CONSUMER passed cases=14' not in text: + raise ValueError('Missing/duplicate inspection consumer checks: ' + name) + resource = re.search(r'^SNAPSHOT_RESOURCE work=(\d+) retained=(\d+) peak=(\d+) nodes=(\d+)$', text, re.M) + if resource is None or not all(int(x) > 0 for x in resource.groups()): + raise ValueError('Missing finite cumulative report: ' + name) + receipt['consumer_results'][name] = dict(cases=checks, work_units=int(resource[1]), + logical_retained_bytes=int(resource[2]), peak_logical_retained_bytes=int(resource[3]), + admitted_nodes=int(resource[4])) + save() + + try: + for config in ['debug', 'release']: + run(config + '-build', ['swift', 'build', '-c', config, '--target', 'JSONInspection']) + binpath = pathlib.Path(run(config + '-binpath', ['swift', 'build', '-c', config, '--show-bin-path']).strip()) + modules = binpath / 'Modules' if (binpath / 'Modules').is_dir() else binpath + objects = [] + for module in ['JSON', 'JSONNavigation', 'JSONInspection']: + aggregate = binpath / (module + '.o') + selected = [aggregate] if aggregate.is_file() else sorted((binpath / (module + '.build')).glob('*.o')) + if not selected: + raise ValueError('Missing built objects: ' + module) + objects.extend(str(p) for p in selected) + common = ['swiftc', '-swift-version', '6', '-warnings-as-errors', '-package-name', + 'snapshot_external_consumer', '-I', str(modules), + '-O' if config == 'release' else '-Onone'] + consumer(config, ['swift', 'run', '-c', config, '--package-path', + 'Tools/SnapshotQualification', 'SnapshotConsumer']) + guide = ROOT / 'Sources/JSONInspection/Documentation.docc/StructuralSnapshots.md' + fences = re.findall(r'```swift\n(.*?)\n```', guide.read_text(), re.S) + if len(fences) != 3: + raise ValueError('Changed three-fence guide inventory') + for index, fence in enumerate(fences): + file = output / ('guide-' + str(index) + '.swift') + file.write_text('import Foundation\nimport JSON\nimport JSONNavigation\nimport JSONInspection\n' + '@main struct Guide { static func main() throws {\n' + fence + '\n} }\n') + binary = output / ('guide-' + config + '-' + str(index)) + run(config + '-guide-' + str(index), common + ['-parse-as-library', str(file), *objects, '-o', str(binary)]) + run(config + '-guide-' + str(index) + '-run', [str(binary)]) + probes = [ + ('core-import-isolation', 'import JSON\nlet x = JSONStructuralSnapshotBuilder.self\n', 'cannot find'), + ('scope-isolation', 'import JSON\nlet x = JSONReadScope.self\n', 'cannot find'), + ('meter-isolation', 'import JSON\nlet x = JSONWorkMeter.self\n', 'cannot find'), + ('capability-isolation', 'import JSONNavigation\nlet x = JSONTraversalContext.self\n', 'cannot find'), + ('reducer-isolation', 'import JSONNavigation\nlet x = JSONTraversalOwnedReducer.self\n', 'cannot find'), + ('record-is-readonly', 'import JSONInspection\nfunc change(_ x: inout JSONStructuralRecord) { x.subtreeEndExclusive = 7 }\n', 'setter is inaccessible'), + ('source-is-readonly', 'import JSON\nimport JSONInspection\nfunc change(_ x: inout JSONStructuralSnapshot) { x.document = .init(root: .null) }\n', "'let' constant"), + ('snapshot-is-unforgeable', 'import JSON\nimport JSONInspection\nlet x = JSONStructuralSnapshot(document: .init(root: .null), records: [])\n', 'initializer is inaccessible'), + ('no-source-rebinding', 'import JSON\nimport JSONInspection\nfunc change(_ x: JSONStructuralSnapshot) throws { _ = try x.value(for: 0, document: .init(root: .null)) }\n', "extra argument 'document'"), + ] + for name, source, expected in probes: + file = output / (name + '.swift'); file.write_text(source) + run(config + '-' + name, common + ['-parse-as-library', '-c', str(file), + '-o', str(output / (config + '-' + name + '.o'))], expected) + consumer('address', ['swift', 'run', '-c', 'release', '--sanitize', 'address', + '--scratch-path', '.build/snapshot-consumer-address', '--package-path', + 'Tools/SnapshotQualification', 'SnapshotConsumer']) + receipt['status'] = 'passed' + except (OSError, ValueError, subprocess.TimeoutExpired) as error: + receipt.update(status='failed', error=str(error)) + save() + print('SNAPSHOT_COMPILER ' + receipt['status'] + + ' configurations=2 consumer_cases=14 address_cases=14 guide_fences=3 negative_probes=18') + return 0 if receipt['status'] == 'passed' else 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Scripts/test_qualification_harness.py b/Scripts/test_qualification_harness.py index 42ce7c7..ea91700 100644 --- a/Scripts/test_qualification_harness.py +++ b/Scripts/test_qualification_harness.py @@ -35,7 +35,7 @@ def test_composition_discovery_and_capacity_limits_are_not_blanket_successes(sel # Combined-source checks must retain both independently owned optional products. for module, count, command in [ ('JSONNavigationTests', 47, ['swift', 'test', '--filter', 'navigation']), - ('JSONInspectionTests', 41, ['swift', 'test', '--filter', 'inspection']), + ('JSONInspectionTests', 44, ['swift', 'test', '--filter', 'inspection']), ('JSONSchemaExamplesTests', 98, ['swift', 'test', '--test-product', 'JSONSchemaExamplesTests']), ]: events = dict(bundle_counts=[count], module_function_counts={module: count}, @@ -49,12 +49,12 @@ def test_composition_discovery_and_capacity_limits_are_not_blanket_successes(sel for key in ('root_functions', 'portable_root_functions', 'wasi_root_functions'): self.assertEqual(policy[key]['JSONNavigationTests'], 47) self.assertEqual(policy[key]['JSONSchemaExamplesTests'], 98) - self.assertEqual(policy[key]['JSONInspectionTests'], 41) + self.assertEqual(policy[key]['JSONInspectionTests'], 44) self.assertEqual(policy[key]['JSONIntegrationTests'], 32) for key in ('shared_state_functions', 'portable_shared_state_functions'): self.assertEqual(policy[key]['JSONNavigationTests'], 1) self.assertEqual(policy[key]['JSONSchemaExamplesTests'], 4) - self.assertEqual(policy[key]['JSONInspectionTests'], 2) + self.assertEqual(policy[key]['JSONInspectionTests'], 3) self.assertEqual(policy[key]['JSONIntegrationTests'], 11) def setUp(self): diff --git a/Sources/JSONCLIEngine/CLIBuildIdentity.swift b/Sources/JSONCLIEngine/CLIBuildIdentity.swift index 469c719..a4d5048 100644 --- a/Sources/JSONCLIEngine/CLIBuildIdentity.swift +++ b/Sources/JSONCLIEngine/CLIBuildIdentity.swift @@ -2,6 +2,6 @@ // Generated by Scripts/generate_cli_identity.py; excludes this file. // SHA-256 of the ordered Package.swift + Sources content manifest, not a Git commit. package enum CLIBuildIdentity { - package static let revision = "sha256:e82189f93a391973c1791e7ff429aa6be3f62b9feaf71d55d91e9e6c6b2cc2c3" + package static let revision = "sha256:81f4a851840b1c2e51064548b3e28f2682f2c7b50867b08f251422c4601aeb30" package static let kind = "source-sha256" } diff --git a/Sources/JSONInspection/Documentation.docc/JSONInspection.md b/Sources/JSONInspection/Documentation.docc/JSONInspection.md index 41ab5b6..5b58e79 100644 --- a/Sources/JSONInspection/Documentation.docc/JSONInspection.md +++ b/Sources/JSONInspection/Documentation.docc/JSONInspection.md @@ -24,3 +24,11 @@ or numeric matrix. Sampling does not exempt any source from admission. - ``JSONShapeKindCounts`` - ``JSONColumnObservation`` - ``JSONShapeInspectionFailure`` + +### Capture complete source structure + +- +- ``JSONStructuralSnapshotBuilder`` +- ``JSONStructuralSnapshot`` +- ``JSONStructuralRecord`` +- ``JSONStructuralSnapshotFailure`` diff --git a/Sources/JSONInspection/Documentation.docc/StructuralSnapshots.md b/Sources/JSONInspection/Documentation.docc/StructuralSnapshots.md new file mode 100644 index 0000000..26e8359 --- /dev/null +++ b/Sources/JSONInspection/Documentation.docc/StructuralSnapshots.md @@ -0,0 +1,85 @@ +# Index complete physical structure + +Build source-attached metadata without replacing the canonical JSON document. + +## Capture even a scalar root + +```swift +let source = JSONDocument(root: .null) +let report = try JSONStructuralSnapshotBuilder(document: source).build() +let snapshot = report.result +let root = try snapshot.record(at: 0) +precondition(snapshot.recordCount == 1) +precondition(root.parentOrdinal == nil && root.componentFromParent == nil) +precondition(root.depth == 0 && root.childCount == 0 && root.subtreeEndExclusive == 1) +let value = try snapshot.value(for: 0) +precondition(value.result == .null) +``` + +The builder always covers one entire physical root, including null and empty +containers. No filter, prefix, prune, depth selection or caller callback can return +an incomplete snapshot. A negative maximumRecords fails configuration before +admission. Zero refuses the real root with a typed capacity error; there is no +successful empty or partial artifact. Quota exhaustion and cancellation throw. + +## Keep paths and numbers exact + +```swift +let source = try JSONParser().parseDocument(Data(#"{"":null,"0":[9007199254740993,0.0100]}"#.utf8)) +let snapshot = try JSONStructuralSnapshotBuilder(document: source).build().result +let rootPath = try snapshot.location(for: 0).result +let emptyKeyPath = try snapshot.location(for: 1).result +let arrayElementPath = try snapshot.location(for: 3).result +precondition(rootPath.isEmpty && emptyKeyPath == [.key("")]) +precondition(arrayElementPath == [.key("0"), .index(0)]) +let integer = try snapshot.value(for: 3).result.numberValue!.integer(as: UInt64.self) +let lexeme = try snapshot.value(for: 4).result.numberValue!.originalLexeme +precondition(integer == 9_007_199_254_740_993 && lexeme == "0.0100") +let root = try snapshot.record(at: 0) +precondition(root.subtreeEndExclusive == snapshot.recordCount) +``` + +Object members retain insertion order and decoded scalar key identity. Array +indices increase from zero. Root [] differs from an empty key; key "0" differs +from index 0. Parent ordinals are lower than children; finalized half-open subtree +ranges include the node and all its descendants, not merely direct children. +Numbers remain in the exact canonical source, with no binary64 projection. + +## Preserve source binding and explicit lookup budgets + +```swift +let document = try JSONParser().parseDocument(Data(#"{"old":null}"#.utf8)) +let snapshot = try JSONStructuralSnapshotBuilder(document: document).build().result +let replacement = document.replacingRoot(.array([42])) +let newer = try JSONStructuralSnapshotBuilder(document: replacement).build().result +let oldPath = try snapshot.location(for: 1) +let newPath = try newer.location(for: 1) +precondition(oldPath.result == [.key("old")] && newPath.result == [.index(0)]) +let oldSpan = try snapshot.sourceSpan(for: 1) +let newSpan = try newer.sourceSpan(for: 1) +precondition(oldSpan.result != nil && newSpan.result == nil) +precondition(oldPath.statistics[.nodes] == 2 && newPath.statistics[.nodes] == 2) +``` + +Ordinals are positions local to the particular snapshot method being called. +A record carries metadata, not a portable node identity. No method accepts a +replacement source or detached handle. An older snapshot keeps its original +immutable source and coherent map; it never follows another document’s revision. +Only original mapped spans are available. Invalid ordinals throw typed bounds +errors before admission rather than returning null, missing or a default record. + +Construction uses Navigation’s original single cumulative read scope and meter. +It transfers the already-admitted document ownership once after complete range +finalization, retaining one source and parent/edge records, without per-node JSON +values, full paths or source maps. Logical overlay v1 reserves 128 fixed bytes, +96 per record, exact edge-key UTF-8 bytes and 16 per open frame, plus original +source/path costs. These policy weights are not Swift heap layout or process RSS. + +record(at:) is O(1) metadata access without opening a new ledger. Each location, +value or sourceSpan call deliberately opens a separate finite operation and +re-admits the whole captured document, even for root or unmapped-span lookup. +Repeated K lookups pay K admissions; they do not share the builder’s remainder. +Path reconstruction also uses depth-sensitive core prefix copies and selection. +The existing traversal builds full hit paths, so total snapshot build work is +not guaranteed linear in node count. No batch session, persistence, stable source +fingerprint, table certification or new JSON representation is provided. diff --git a/Sources/JSONInspection/JSONStructuralSnapshot.swift b/Sources/JSONInspection/JSONStructuralSnapshot.swift index 5ad64c3..52ba154 100644 --- a/Sources/JSONInspection/JSONStructuralSnapshot.swift +++ b/Sources/JSONInspection/JSONStructuralSnapshot.swift @@ -2,42 +2,69 @@ import JSON import JSONNavigation -/// Experimental metadata only; ordinal identity is local to one snapshot. -package struct JSONStructuralRecord: Sendable { - package let ordinal: Int - package let parentOrdinal: Int? - package let componentFromParent: JSONLocationComponent? - package let kind: JSONTraversalKind - package let depth: Int - package let childCount: Int - package internal(set) var subtreeEndExclusive: Int +/// Read-only DFS preorder metadata for one physical node in a complete snapshot. +/// Ordinals are local positions in the containing snapshot, not transferable node +/// identities. A record cannot resolve a value or attach to a replacement source. +public struct JSONStructuralRecord: Sendable { + /// Zero-based preorder position within the containing snapshot. + public let ordinal: Int + /// Parent position, strictly lower than this ordinal; nil only for the root. + public let parentOrdinal: Int? + /// Exact key or array index from the parent; nil only for the physical root. + public let componentFromParent: JSONLocationComponent? + /// Canonical JSON kind; no inferred schema, missing or table kind. + public let kind: JSONTraversalKind + /// Number of physical edges from the root, whose depth is zero. + public let depth: Int + /// Number of immediate children; a scalar or empty container has zero. + public let childCount: Int + /// Finalized exclusive preorder end; this node’s subtree is [ordinal, end). + public internal(set) var subtreeEndExclusive: Int } -package enum JSONStructuralSnapshotFailure: Error, Sendable, Equatable { +/// A capacity or local-position refusal; no partial snapshot or null substitute. +public enum JSONStructuralSnapshotFailure: Error, Sendable, Equatable { + /// The next physical node exceeds the inclusive record bound. + /// - Parameter limit: Nonnegative maximum configured for this build. case recordLimitExceeded(limit: Int) + /// The supplied local position is outside 0.. JSONStructuralRecord { + /// Reads one metadata record in O(1), without opening a new resource ledger. + /// This trusted-memory access performs no path/value scan or owned growth. + /// - Parameter ordinal: Position local to this snapshot, not a detached node ID. + /// - Returns: Read-only metadata with a finalized half-open subtree range. + /// - Throws: `JSONStructuralSnapshotFailure.ordinalOutOfBounds` for an invalid position. + public func record(at ordinal: Int) throws -> JSONStructuralRecord { guard ordinal >= 0, ordinal < records.count else { throw JSONStructuralSnapshotFailure.ordinalOutOfBounds(ordinal) } return records[ordinal] } - /// Separate owned lookup; all parent-chain and path work uses its own scope. - package func location(for ordinal: Int, limits: JSONLimits = .balanced) + /// Reconstructs an exact typed location in a separate bounded operation. + /// Re-admits the entire captured root/source/map before parent-chain/path work. + /// Core prefix copying can be quadratic in depth; this is not O(1) lookup. + /// - Parameters: + /// - ordinal: Position local to this snapshot; bounds checked before admission. + /// - limits: Finite quotas for this new lookup, independent of construction. + /// - Returns: Exact key/index path and cumulative lookup statistics; [] is root. + /// - Throws: Typed bounds, core resource/configuration failures or cancellation. + public func location(for ordinal: Int, limits: JSONLimits = .balanced) throws -> JSONOperationReport<[JSONLocationComponent]> { try lookup(ordinal, limits: limits) { path, state, _ in // Output path ownership survives scope completion's temporary refund. @@ -51,9 +78,16 @@ package struct JSONStructuralSnapshot: Sendable { } } - /// Resolve only against this captured source, using the core strict selector. - /// Returned JSON follows existing read-value alias semantics, not a copied DOM. - package func value(for ordinal: Int, limits: JSONLimits = .balanced) + /// Resolves a value only against the captured source in a new bounded operation. + /// Each call re-admits the full source and reconstructs its typed path before + /// core selection. The result uses existing immutable read-value alias semantics; + /// no JSON subtree is cached in a record or converted through binary floating point. + /// - Parameters: + /// - ordinal: Position local to this snapshot; invalid positions never become null. + /// - limits: Finite quotas for this separate lookup, not the builder’s remainder. + /// - Returns: The canonical value, including explicit null, and lookup statistics. + /// - Throws: Typed bounds, core access/resource/configuration errors or cancellation. + public func value(for ordinal: Int, limits: JSONLimits = .balanced) throws -> JSONOperationReport { try lookup(ordinal, limits: limits) { path, state, scope in try state.meter.work() @@ -61,7 +95,15 @@ package struct JSONStructuralSnapshot: Sendable { } } - package func sourceSpan(for ordinal: Int, limits: JSONLimits = .balanced) + /// Looks up a coherent original value span in a new bounded operation. + /// Full captured-source admission and path reconstruction occur even without a + /// source map. A replacement/unmapped document has no invented historical span. + /// - Parameters: + /// - ordinal: Position local to this snapshot; bounds checked before admission. + /// - limits: Finite quotas for this separate source-map lookup. + /// - Returns: Original mapped span or nil when unavailable, plus lookup statistics. + /// - Throws: Typed bounds, core resource/configuration failures or cancellation. + public func sourceSpan(for ordinal: Int, limits: JSONLimits = .balanced) throws -> JSONOperationReport { try lookup(ordinal, limits: limits) { path, state, _ in try document.sourceMap?.span(at: path, kind: .value, meter: &state.meter) @@ -104,16 +146,35 @@ package struct JSONStructuralSnapshot: Sendable { } } -/// Complete physical-root only; no filter, prune, prefix or custom callback. -package struct JSONStructuralSnapshotBuilder: Sendable { - package let document: JSONDocument - package let limits: JSONLimits +/// Builds complete source-attached metadata with one original cumulative ledger. +/// No filtering, pruning, prefix, callback, per-node JSON copy or second admission. +/// Records retain parent/edge metadata and one immutable document. Existing walker +/// hit paths mean total construction is depth-sensitive, not guaranteed O(nodes). +/// Logical overlay v1 is 128 fixed bytes, 96/record, exact UTF-8 edge-key bytes and +/// 16/live open frame, plus source/path admission. These weights are not heap/RSS. +public struct JSONStructuralSnapshotBuilder: Sendable { + /// Canonical immutable source; admitted once when build begins. + public let document: JSONDocument + /// Finite construction quotas shared by admission, traversal and finalization. + public let limits: JSONLimits package var checkpointObserver: (@Sendable (UInt64) -> Void)? package var lifetimeObserver: (@Sendable (Bool, Bool) -> Void)? - package init(document: JSONDocument, limits: JSONLimits = .balanced) { + /// Captures trusted source and policy; this initializer performs no admission. + /// - Parameters: + /// - document: Physical canonical root, optionally with coherent source mapping. + /// - limits: One finite profile for the complete construction invocation. + public init(document: JSONDocument, limits: JSONLimits = .balanced) { self.document = document; self.limits = limits } - package func build(maximumRecords: Int = 100_000) + /// Returns every physical node with finalized ranges, or throws without an artifact. + /// Negative capacity fails before admission; zero refuses the real root. Even + /// null and empty containers have one record. Source ownership transfers once + /// only after complete traversal; temporary scope references are invalidated. + /// - Parameter maximumRecords: Inclusive nonnegative bound, default 100,000. + /// - Returns: Immutable complete snapshot and cumulative construction statistics. + /// - Throws: Typed capacity refusal, invalid configuration, resource exhaustion + /// or cancellation. No successful partial snapshot or terminal-error override. + public func build(maximumRecords: Int = 100_000) throws -> JSONOperationReport { guard maximumRecords >= 0 else { throw JSONFailure(.configuration, code: .invalidLimits) } var reducer = SnapshotReducer(maximumRecords: maximumRecords) diff --git a/Tests/JSONInspectionTests/SnapshotPromotionTests.swift b/Tests/JSONInspectionTests/SnapshotPromotionTests.swift new file mode 100644 index 0000000..9823dae --- /dev/null +++ b/Tests/JSONInspectionTests/SnapshotPromotionTests.swift @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import Synchronization +import Testing +import JSON +import JSONNavigation +import JSONInspection + +// Independent NAV6 packet oracle; no expected topology is generated by the walker. +@Test func snapshotV12IndependentMixedOracleMatchesEveryRecord() throws { + let text = "{\n \"\": {\"0\": [null, {\"id\": 9007199254740993, \"amount\": 0.0100}]},\n \"0\": [\"numeric object key, not index\"],\n \"é\": \"composed\",\n \"é\": \"decomposed\",\n \"rows\": [\n {\"z\": null, \"a\": 1},\n {\"a\": 2},\n {},\n {\"b\": 3, \"a\": 4},\n false\n ],\n \"matrix\": [[0.1, 2], [3]]\n}\n" + let result = try JSONStructuralSnapshotBuilder(document: JSONParser().parseDocument(Data(text.utf8))).build().result + let expected: [(Int?, JSONLocationComponent?, JSONTraversalKind, Int, Int, Int)] = [ + (nil, nil, .object, 0, 6, 28), + (0, .key(""), .object, 1, 1, 7), + (1, .key("0"), .array, 2, 2, 7), + (2, .index(0), .null, 3, 0, 4), + (2, .index(1), .object, 3, 2, 7), + (4, .key("id"), .number, 4, 0, 6), + (4, .key("amount"), .number, 4, 0, 7), + (0, .key("0"), .array, 1, 1, 9), + (7, .index(0), .string, 2, 0, 9), + (0, .key("é"), .string, 1, 0, 10), + (0, .key("é"), .string, 1, 0, 11), + (0, .key("rows"), .array, 1, 5, 22), + (11, .index(0), .object, 2, 2, 15), + (12, .key("z"), .null, 3, 0, 14), + (12, .key("a"), .number, 3, 0, 15), + (11, .index(1), .object, 2, 1, 17), + (15, .key("a"), .number, 3, 0, 17), + (11, .index(2), .object, 2, 0, 18), + (11, .index(3), .object, 2, 2, 21), + (18, .key("b"), .number, 3, 0, 20), + (18, .key("a"), .number, 3, 0, 21), + (11, .index(4), .boolean, 2, 0, 22), + (0, .key("matrix"), .array, 1, 2, 28), + (22, .index(0), .array, 2, 2, 26), + (23, .index(0), .number, 3, 0, 25), + (23, .index(1), .number, 3, 0, 26), + (22, .index(1), .array, 2, 1, 28), + (26, .index(0), .number, 3, 0, 28), + ] + #expect(result.recordCount == expected.count) + for ordinal in expected.indices { + let record = try result.record(at: ordinal), want = expected[ordinal] + #expect(record.ordinal == ordinal && record.parentOrdinal == want.0) + #expect(record.componentFromParent == want.1 && record.kind == want.2) + #expect(record.depth == want.3 && record.childCount == want.4 && record.subtreeEndExclusive == want.5) + } +} + +private enum TransferMarker: Error { case afterTransfer } +private struct TransferProbe: JSONTraversalOwnedReducer { + enum Mode: Sendable { case twice, work, caller, cancellation } + let mode: Mode + mutating func observe(_ hit: JSONTraversalHit, value: JSON, + context: inout JSONTraversalContext) throws -> JSONTraversalAction { .continue } + mutating func finish(_ summary: JSONTraversalSummary, + context: inout JSONTraversalContext) throws -> JSONDocument { + let document = try context.transferDocumentOwnership() + switch mode { + case .twice: + do { _ = try context.transferDocumentOwnership() } catch { } + case .work: + do { try context.work(64_000_000) } catch { } + case .cancellation: + withUnsafeCurrentTask { $0?.cancel() } + do { try context.checkpoint() } catch { } + case .caller: throw TransferMarker.afterTransfer + } + // Success/replacement cannot mask a terminal failure latched after transfer. + if mode == .twice { return document } + throw TransferMarker.afterTransfer + } +} + +@Test func snapshotV20TransferFailureCannotPublishAndScopeAlwaysInvalidates() async throws { + let document = try JSONParser().parseDocument(Data("[null]".utf8)) + for mode in [TransferProbe.Mode.twice, .work, .caller, .cancellation] { + let invalidated = Mutex(false) + let task = Task { @Sendable [document, mode] in + var walk = JSONTraversal(document: document) + walk.lifetimeObserver = { rootGone, stateGone in invalidated.withLock { $0 = rootGone && stateGone } } + var probe = TransferProbe(mode: mode) + return try walk.inspectOwned(reducer: &probe) + } + do { _ = try await task.value; Issue.record("No post-transfer failure may publish a report") } + catch let error as JSONFailure { + if mode == .twice { #expect(error.category == .configuration && error.code == .invalidOperation) } + else { #expect(mode == .work && error.resource?.metric == .workUnits) } + } + catch is CancellationError { #expect(mode == .cancellation) } + catch is TransferMarker { #expect(mode == .caller) } + #expect(invalidated.withLock { $0 }) + } + // Ordinary read/visit/N2 have no ownership transfer: their refunds are unchanged. + #expect(try document.readWithReport { _ in true }.statistics[.retainedBytes] == 0) + #expect(try JSONTraversal(document: document).visit { _, _ in .continue }.statistics[.retainedBytes] == 0) + let shape = try JSONShapeInspector(document: document).inspectArray() + #expect(shape.statistics[.retainedBytes] == 256) +} + +@Test func snapshotV22ConcurrentLookupsUseIndependentAdmissionAndHealth() async throws { + let document = try JSONParser().parseDocument(Data(#"{"x":[null,9007199254740993]}"#.utf8)) + let snapshot = try JSONStructuralSnapshotBuilder(document: document).build().result + let baseline = try snapshot.value(for: 3) + try await withThrowingTaskGroup(of: UInt64.self) { group in + for ordinal in 0..<16 { group.addTask { + if ordinal.isMultiple(of: 2) { + var limits = JSONLimits.balanced; limits[.nodes] = 0 + do { _ = try snapshot.value(for: 3, limits: limits); Issue.record("Expected source-admission refusal") } + catch let error as JSONFailure { #expect(error.resource?.metric == .nodes) } + } + let value = try snapshot.value(for: 3) + #expect(value.result == baseline.result) + #expect(value.statistics[.nodes] == UInt64(snapshot.recordCount)) + #expect(try snapshot.location(for: 3).result == [.key("x"), .index(1)]) + #expect(try snapshot.sourceSpan(for: 3).result != nil) + return value.statistics[.workUnits] + } } + for try await work in group { #expect(work == baseline.statistics[.workUnits]) } + } +} diff --git a/Tools/SnapshotQualification/Package.swift b/Tools/SnapshotQualification/Package.swift new file mode 100644 index 0000000..0fec6e8 --- /dev/null +++ b/Tools/SnapshotQualification/Package.swift @@ -0,0 +1,12 @@ +// swift-tools-version: 6.4 +// SPDX-License-Identifier: Apache-2.0 +import PackageDescription +let package = Package( + name: "SnapshotQualification", + platforms: [.macOS("27.0"), .iOS("27.0")], + dependencies: [.package(name: "Swift-JSON", path: "../..")], + targets: [.executableTarget(name: "SnapshotConsumer", dependencies: [ + .product(name: "JSON", package: "Swift-JSON"), + .product(name: "JSONNavigation", package: "Swift-JSON"), + .product(name: "JSONInspection", package: "Swift-JSON")])], + swiftLanguageModes: [.v6]) diff --git a/Tools/SnapshotQualification/README.md b/Tools/SnapshotQualification/README.md new file mode 100644 index 0000000..818e0df --- /dev/null +++ b/Tools/SnapshotQualification/README.md @@ -0,0 +1,10 @@ +# Public snapshot consumer + +This independent SwiftPM package imports only public JSON, JSONNavigation and +JSONInspection APIs. Run `swift run -c release --package-path +Tools/SnapshotQualification SnapshotConsumer` on a qualified Swift 6.4 platform. +Its fourteen named checks exercise exact source-bound metadata and separate +bounded lookups, including negative capacity, bounds and quota cases. It does +not establish a release or physical-device qualification. The associated +`Scripts/qualify_snapshot.py` executes all three DocC examples and full-codegen +external-access rejections, with source-bound receipts. diff --git a/Tools/SnapshotQualification/Sources/SnapshotConsumer/Main.swift b/Tools/SnapshotQualification/Sources/SnapshotConsumer/Main.swift new file mode 100644 index 0000000..13b0590 --- /dev/null +++ b/Tools/SnapshotQualification/Sources/SnapshotConsumer/Main.swift @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONNavigation +import JSONInspection + +private func expect(_ condition: @autoclosure () throws -> Bool) throws { + let satisfied = try condition(); precondition(satisfied) +} +private func check(_ name: String, _ body: () throws -> Void) rethrows { + try body(); print("SNAPSHOT_CHECK \(name) passed") +} +private func failure(_ body: () throws -> Void) -> JSONFailure { + do { try body(); preconditionFailure("Expected failure") } + catch let error as JSONFailure { return error } + catch { preconditionFailure("Unexpected error: \(error)") } +} +private func sendable(_ value: T) { _ = value } + +@main struct SnapshotConsumer { + static func main() async throws { + let source = #"{"":null,"0":[9007199254740993,0.1,0.0100],"é":true,"e\u0301":false,"/":{"~":[]}}"# + let document = try JSONParser().parseDocument(Data(source.utf8)) + let builder = JSONStructuralSnapshotBuilder(document: document) + let report = try builder.build() + let snapshot = report.result + sendable(builder); sendable(snapshot); sendable(try snapshot.record(at: 0)) + try check("roots") { + for text in ["null", "false", "1", #""s""#, "{}", "[]"] { + let one = try JSONStructuralSnapshotBuilder(document: .init(root: JSON.parse(text))).build().result + try expect(one.recordCount == 1) + let root = try one.record(at: 0) + try expect(root.ordinal == 0 && root.parentOrdinal == nil && root.componentFromParent == nil) + try expect(root.childCount == 0 && root.depth == 0 && root.subtreeEndExclusive == 1) + } + } + try check("typed-paths") { + try expect(try snapshot.location(for: 0).result == []) + try expect(try snapshot.location(for: 1).result == [.key("")]) + try expect(try snapshot.location(for: 2).result == [.key("0")]) + try expect(try snapshot.location(for: 3).result == [.key("0"), .index(0)]) + try expect(try snapshot.record(at: 2).componentFromParent != .index(0)) + } + try check("unicode") { + try expect(try snapshot.record(at: 6).componentFromParent == .key("é")) + try expect(try snapshot.record(at: 7).componentFromParent == .key("e\u{301}")) + try expect(try snapshot.record(at: 6).componentFromParent != snapshot.record(at: 7).componentFromParent) + } + try check("numbers") { + try expect(try snapshot.value(for: 3).result.numberValue?.integer(as: UInt64.self) == 9_007_199_254_740_993) + try expect(try snapshot.value(for: 4).result.numberValue == JSONNumber(parsing: "0.1")) + try expect(try snapshot.value(for: 5).result.numberValue?.originalLexeme == "0.0100") + try expect(try snapshot.value(for: 1).result == .null) + } + try check("duplicate-refusal") { + let error = failure { _ = try JSONParser().parse(#"{"a":0,"\u0061":1}"#) } + try expect(error.code == .duplicateKey) + } + try check("preorder-ranges") { + let expected = [10,2,6,4,5,6,7,8,10,10] + try expect(snapshot.recordCount == expected.count) + for ordinal in 0.. 0) + } + try expect(report.statistics[.nodes] == UInt64(snapshot.recordCount)) + var noNodes = JSONLimits.balanced; noNodes[.nodes] = 0 + try expect(failure { _ = try snapshot.value(for: 3, limits: noNodes) }.resource?.metric == .nodes) + print("SNAPSHOT_RESOURCE work=\(report.statistics[.workUnits]) retained=\(report.statistics[.retainedBytes]) peak=\(report.statistics.peakLogicalRetainedBytes) nodes=\(report.statistics[.nodes])") + } + try check("capacity-configuration") { + try expect(try builder.build(maximumRecords: snapshot.recordCount).result.recordCount == snapshot.recordCount) + for cap in [0, snapshot.recordCount-1] { + do { _ = try builder.build(maximumRecords: cap); preconditionFailure("No partial success") } + catch let error as JSONStructuralSnapshotFailure { try expect(error == .recordLimitExceeded(limit: cap)) } + } + var limits = JSONLimits.balanced; limits[.nodes] = 0 + try expect(failure { _ = try JSONStructuralSnapshotBuilder(document: document, limits: limits).build(maximumRecords: -1) }.category == .configuration) + } + try check("inclusive-budgets") { + var limits = JSONLimits.balanced; limits[.workUnits] = report.statistics[.workUnits] + try expect(try JSONStructuralSnapshotBuilder(document: document, limits: limits).build().result.recordCount == snapshot.recordCount) + limits[.workUnits] -= 1 + try expect(failure { _ = try JSONStructuralSnapshotBuilder(document: document, limits: limits).build() }.resource?.metric == .workUnits) + limits = .balanced; limits[.retainedBytes] = report.statistics.peakLogicalRetainedBytes + _ = try JSONStructuralSnapshotBuilder(document: document, limits: limits).build() + limits[.retainedBytes] -= 1 + try expect(failure { _ = try JSONStructuralSnapshotBuilder(document: document, limits: limits).build() }.resource?.metric == .retainedBytes) + } + try check("coverage-separation") { + let rows = try JSON.parse(#"[{"a":null},{"b":1},false]"#) + let evidence = try JSONShapeInspector(document: .init(root: rows)).inspectArray(sampling: .prefix(maximumRows: 2)).result + try expect(evidence.rowCoverage == .prefix(examined: 2, total: 3)) + try expect(evidence.observedAllObjects == true) + try expect(try JSONStructuralSnapshotBuilder(document: .init(root: rows)).build().result.recordCount == 6) + } + try await withThrowingTaskGroup(of: UInt64.self) { group in + for _ in 0..<4 { group.addTask { try snapshot.value(for: 3).statistics[.workUnits] } } + let expected = try snapshot.value(for: 3).statistics[.workUnits] + for try await work in group { try expect(work == expected) } + } + print("SNAPSHOT_CHECK concurrent-lookups passed") + print("SNAPSHOT_CONSUMER passed cases=14") + } +}