From 526e47dfd3552e81177664adcc28f067654b5131 Mon Sep 17 00:00:00 2001 From: Thorsten Fuchs <76904008+ThorFuchs@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:01:48 +0200 Subject: [PATCH 1/6] feat(cli): add bounded strict JSON command foundation --- AGENTS.md | 5 +- .../Architecture/ExtractionManifest.json | 3 +- .../Architecture/Release1.4/Acceptance.json | 1186 +++++++++++++++++ .../Architecture/Release1.4/Foundation.md | 103 ++ Documentation/Architecture/Release1.4/Plan.md | 40 + .../Architecture/Release1.4/Progress.json | 41 + .../Architecture/Release1.4/Qualification.md | 50 + Documentation/README.md | 2 + Package.swift | 7 +- README.md | 16 + Scripts/ci.py | 20 +- Scripts/qualify_cli.py | 109 ++ Scripts/qualify_cli_ownership.py | 21 + Scripts/testing_events.py | 1 + .../JSON/Documentation.docc/CommandLine.md | 36 + Sources/JSON/Limits/JSONWorkMeter.swift | 19 +- Sources/JSON/Pointer/JSONPointer.swift | 29 +- Sources/JSONCLI/Main.swift | 26 + Sources/JSONCLIEngine/CLIArguments.swift | 89 ++ Sources/JSONCLIEngine/CLIDiagnostics.swift | 115 ++ Sources/JSONCLIEngine/CLIEngine.swift | 225 ++++ Sources/JSONCLIEngine/CLIFileIO.swift | 154 +++ .../JSONCLIEngine/CLIProcessArguments.swift | 46 + Sources/JSONFilterRuntime/CLIJobContext.swift | 153 +++ Sources/JSONPatch/JSONPatch.swift | 14 +- .../Compilation/JSONSchemaCompiler.swift | 7 + .../Evaluation/JSONSchemaValidator.swift | 6 +- Tests/JSONCLITests/FoundationTests.swift | 135 ++ 28 files changed, 2640 insertions(+), 18 deletions(-) create mode 100644 Documentation/Architecture/Release1.4/Acceptance.json create mode 100644 Documentation/Architecture/Release1.4/Foundation.md create mode 100644 Documentation/Architecture/Release1.4/Plan.md create mode 100644 Documentation/Architecture/Release1.4/Progress.json create mode 100644 Documentation/Architecture/Release1.4/Qualification.md create mode 100644 Scripts/qualify_cli.py create mode 100644 Scripts/qualify_cli_ownership.py create mode 100644 Sources/JSON/Documentation.docc/CommandLine.md create mode 100644 Sources/JSONCLI/Main.swift create mode 100644 Sources/JSONCLIEngine/CLIArguments.swift create mode 100644 Sources/JSONCLIEngine/CLIDiagnostics.swift create mode 100644 Sources/JSONCLIEngine/CLIEngine.swift create mode 100644 Sources/JSONCLIEngine/CLIFileIO.swift create mode 100644 Sources/JSONCLIEngine/CLIProcessArguments.swift create mode 100644 Sources/JSONFilterRuntime/CLIJobContext.swift create mode 100644 Tests/JSONCLITests/FoundationTests.swift diff --git a/AGENTS.md b/AGENTS.md index 443ee58..b8aa949 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,8 +4,9 @@ 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 five public products: JSON, JSONSchema, JSONStreaming, JSONPatch and JSONHTML. - JSONHTML depends on JSON only; the maintainer-approved 1.3 addition preserves historical four-product releases. +- Keep six public products: JSON, JSONSchema, JSONStreaming, JSONPatch, JSONHTML + and the swift-json executable. JSONFilterRuntime and JSONCLIEngine are package-private. + JSONHTML depends on JSON only. The approved 1.4 CLI addition preserves historical product contracts. JSONRegexRuntime is package-private. Root package dependencies remain empty. - Preserve exact bounded decimals, decoded Unicode scalar identity, ordered objects with order-independent equality, one strict UTF-8 grammar, native diff --git a/Documentation/Architecture/ExtractionManifest.json b/Documentation/Architecture/ExtractionManifest.json index 735d569..66bf9f6 100644 --- a/Documentation/Architecture/ExtractionManifest.json +++ b/Documentation/Architecture/ExtractionManifest.json @@ -196,7 +196,8 @@ "Schema graph, explicit evaluator, exact sets/assertions and scalar format recognizers are original implementations from Draft 2020-12, RFC3986/3987/3339/3492/5321/5891/5892/5893/6531/6570/6901 and fixed Unicode13 data.", "Private FIPS 180-4 SHA-256 is an original Swift implementation with checked cumulative production accounting, bounded scratch and inner cancellation. The separate trusted fixture tool mirrors its primitive core byte-for-byte; public_check verifies parity. No CryptoKit fallback or runtime dependency is introduced.", "JSONHTML formatted rendering, original fixed CSS and shared strict-writer token classification are original Apache-2.0 code. No third-party renderer, browser numeric authority or runtime dependency is adapted.", - "JSONHTML typed annotations, iterative exact-path binding, static/interactive attachment and coherent JSONDocument span presentation are original Apache-2.0 code. No schema engine, third-party renderer, source archive or new dependency is adapted." + "JSONHTML typed annotations, iterative exact-path binding, static/interactive attachment and coherent JSONDocument span presentation are original Apache-2.0 code. No schema engine, third-party renderer, source archive or new dependency is adapted.", + "CLI foundation, argv parser, POSIX adapters, prepaid bounded diagnostics and shared-meter package adapters are original Apache-2.0 Swift code. No legacy filter engine or jq source is copied. See Release1.4/Foundation.md for native test-harness reuse provenance." ], "policy": "Record actual adaptations and retain applicable upstream copyrights and licenses. Reference inputs and comparison experiments are not distributed runtime dependencies.", "jsonhtml_export_original_contributions": { diff --git a/Documentation/Architecture/Release1.4/Acceptance.json b/Documentation/Architecture/Release1.4/Acceptance.json new file mode 100644 index 0000000..5b17493 --- /dev/null +++ b/Documentation/Architecture/Release1.4/Acceptance.json @@ -0,0 +1,1186 @@ +{ + "schema": "swift-json-cli-acceptance/1", + "prepared": "2026-10-14", + "implementation_status": "foundation boundary only; integration unqualified", + "requirements": [ + { + "id": "CL001", + "gate": "C0", + "title": "Live source/prerequisite reconciliation", + "verification": "Record actual main/base/HTML/provider heads and compare pins; preserve active work and dirty state.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL002", + "gate": "C0", + "title": "Six public products with correct kinds", + "verification": "Package dump contains five libraries plus swift-json executable; private targets have no products.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL003", + "gate": "C0", + "title": "Empty root dependency set", + "verification": "Dump/package resolution and clean-source build prove no external dependency was introduced.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL004", + "gate": "C0", + "title": "Dependency direction and mobile consumers", + "verification": "Selected library consumers have no CLI/filter dependencies; core has no HTML/provider/UI imports.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL005", + "gate": "C0", + "title": "Forward policy and historical records", + "verification": "Update AGENTS/README/CI prospective inventory; historical releases remain accurate.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL006", + "gate": "C0", + "title": "Private compiler/IR ownership", + "verification": "Compiler probes prove immutable checked-Sendable plans and invocation-owned noncopyable context.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL007", + "gate": "C0", + "title": "Attribution and extraction manifest", + "verification": "Every actual adaptation has pinned origin/status/notices; inherited files retain identity.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL008", + "gate": "C0", + "title": "Prior brief conflict resolution", + "verification": "Record reused HTML/tab/provider APIs and authoritative amendments; no duplicate conformance or restarted portability.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL009", + "gate": "C1", + "title": "Argv parsing and limits", + "verification": "Exercise long equals, grouped Boolean flags, leading-dash filenames, --, duplicates, unknown/conflicting options and bounded args.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [ + "unknown-option" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL010", + "gate": "C1", + "title": "Truthful help/version", + "verification": "Only available qualified commands appear; missing command/config/capability is 2; help/version is 0.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL011", + "gate": "C1", + "title": "Strict native format", + "verification": "Single root, full EOF, native UTF-8/duplicate-key grammar, exact lexemes and default two-space formatting.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [ + "format-preserved-lexemes", + "reject-duplicate-key", + "reject-trailing-root", + "reject-trailing-comma" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL012", + "gate": "C1", + "title": "Explicit presentation options", + "verification": "Compact/indent/tab/sort/number-style options preserve library defaults and scalar key order.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [ + "conflicting-style-options" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL013", + "gate": "C1", + "title": "Syntax validation outcomes", + "verification": "Completed valid/invalid result and issues; malformed input 5, resource 6, IO 7 are separate.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL014", + "gate": "C1", + "title": "Offline schema validation", + "verification": "Compile once per job; explicit immutable resources; instance invalid 1; no fetch/default insertion.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [ + "validate-instance-invalid" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL015", + "gate": "C1", + "title": "Pointer presence", + "verification": "Present null versus missing, escaped tokens, root pointer, allow-missing exit4 and type/domain errors.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [ + "pointer-present-null", + "pointer-missing-allowed", + "pointer-escaped-key" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL016", + "gate": "C1", + "title": "Patch transactional candidate", + "verification": "Successful operations emit full result; failed test/path never emits staged mutation or changes source.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [ + "patch-success", + "patch-failed-test" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL017", + "gate": "C1", + "title": "Incremental local IO", + "verification": "Owned chunk reads, explicit closure, split UTF-8 and partial/no-progress writes; no read-to-end job arrays.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL018", + "gate": "C1", + "title": "Stdout/stderr separation", + "verification": "All data is clean; JSON diagnostics/reports are tagged stderr lines with no ANSI/banner.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL019", + "gate": "C1", + "title": "Shared compound accounting", + "verification": "Nested parse/validate/patch/write and multiple records cannot reset work or retained overlap; failure latches.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL020", + "gate": "C1", + "title": "Every CLI cap boundary", + "verification": "Inclusive-cap and cap+1 sweeps for all 16 CLI caps; checked counters and conversion overflow.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL021", + "gate": "C1", + "title": "Existing native limits preserved", + "verification": "49 metric inventory/meaning and existing independent boundary tests remain intact.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL022", + "gate": "C1", + "title": "Actual in-loop cancellation", + "verification": "Checkpoint rendezvous inside long owned work and source/sink suspend; no further output after terminal failure.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL023", + "gate": "C1", + "title": "Atomic file success", + "verification": "Owned same-directory stage installed only after EOF/flush/close; report committed completion.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL024", + "gate": "C1", + "title": "Atomic file failure", + "verification": "Late invalid tail/resource/cancel/IO preserves destination and removes only owned stage.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL025", + "gate": "C1", + "title": "Overwrite/path policy", + "verification": "Existing file requires --overwrite; input/output alias and output symlink reject; no silent cross-FS fallback.", + "obligation": "mandatory", + "status": "passed", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL026", + "gate": "C1", + "title": "Exit precedence", + "verification": "Codes0/1/2/3/4/5/6/7/130 and completed-result precedence verified in actual subprocesses.", + "obligation": "mandatory", + "status": "partial", + "evidence_required": true, + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Qualification.md" + ] + }, + { + "id": "CL027", + "gate": "C2", + "title": "Native query lexer/literals", + "verification": "Exact numbers and scalar text/keys with source byte ranges; malformed source compiles to exit3.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL028", + "gate": "C2", + "title": "Grammar precedence", + "verification": "Pipeline/comma/default/assignment/operator precedence and delimiters tested beyond old parser goldens.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "pipeline-comma-precedence" + ] + }, + { + "id": "CL029", + "gate": "C2", + "title": "Pull VM cardinality", + "verification": "Empty/one/many outputs without whole-job array; next() demand limits observable reads/retention.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL030", + "gate": "C2", + "title": "Pipeline and comma order", + "verification": "Every upstream result is evaluated and ordered; late errors do not resume a failed branch.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL031", + "gate": "C2", + "title": "Cartesian operators/constructors", + "verification": "No .first collapse; object versus arithmetic nesting orders and shared input effects are tested.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "object-product-order" + ] + }, + { + "id": "CL032", + "gate": "C2", + "title": "Navigation and wrong-type", + "verification": "Missing/null convention, has, wrong-type error, optional catch and checked indices/slices.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "missing-field-null", + "wrong-type-field", + "optional-wrong-type-empty", + "has-present-null", + "negative-index" + ] + }, + { + "id": "CL033", + "gate": "C2", + "title": "Exact Unicode identity", + "verification": "Scalar-distinct strings/keys, scalar length/slicing/order and non-BMP behavior.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "scalar-key-lookup", + "scalar-string-inequality", + "scalar-length", + "utf8-length", + "scalar-slice" + ] + }, + { + "id": "CL034", + "gate": "C2", + "title": "Truth/default/Boolean generators", + "verification": "Only false/null falsey; // handles full streams; left-driven short-circuit and if generators.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "truthiness", + "fallback-whole-generator", + "fallback-empty", + "Boolean-short-circuit", + "if-condition-stream" + ] + }, + { + "id": "CL035", + "gate": "C2", + "title": "Constructed duplicate keys", + "verification": "Explicit replace-preserve-position policy; parsed exact duplicate keys still reject.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "constructed-key-replacement" + ] + }, + { + "id": "CL036", + "gate": "C2", + "title": "Exact comparison/order", + "verification": "Metered decimal/string/array/object equality and total order with stable ties.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL037", + "gate": "C2", + "title": "Common builtin inventory", + "verification": "All C2 builtins and type/domain/empty cases; map collects and map_values updates/deletes correctly.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "empty-array-collector", + "map-all-results", + "map-values-first", + "map-values-delete", + "normals-nonzero-native" + ] + }, + { + "id": "CL038", + "gate": "C2", + "title": "Lexical bindings/arguments", + "verification": "as, --arg/argjson/rawfile/slurpfile, $ARGS, duplicate/reserved names, strict auxiliary-file admission.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "binding-original-input", + "argjson-exact" + ] + }, + { + "id": "CL039", + "gate": "C2", + "title": "Shared input cursor", + "verification": "Driver/input/inputs share consumption; EOF catchable only for input; malformed tails/IO remain fatal.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "null-input-once", + "shared-inputs" + ] + }, + { + "id": "CL040", + "gate": "C2", + "title": "JSON/raw framing", + "verification": "values/document/NDJSON and strict raw UTF-8 lines/slurp; files have independent boundaries.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "values-framing", + "adjacent-roots-rejected", + "ndjson-blank-line-rejected", + "raw-line-crlf" + ] + }, + { + "id": "CL041", + "gate": "C2", + "title": "Raw output/NUL/join", + "verification": "Correct string bytes and nonstring JSON; embedded NUL rejected; delimiters/errors/counts tested.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "raw-lf", + "raw-nul-delimiter", + "raw-nul-value-rejected", + "join-no-delimiter" + ] + }, + { + "id": "CL042", + "gate": "C2", + "title": "ASCII output and sort keys", + "verification": "Owned escaping, surrogate pairs and exact scalar order; option conflicts tested.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "ascii-surrogate-pair" + ] + }, + { + "id": "CL043", + "gate": "C2", + "title": "Query exit-status", + "verification": "Final semantic value only after completion; truthy0/empty string/collections; false/null1 and noresult4.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "exit-status-truthy-zero", + "exit-status-false", + "exit-status-empty" + ] + }, + { + "id": "CL044", + "gate": "C2", + "title": "Plan reuse and isolation", + "verification": "Concurrent independent invocations share immutable plan only; mutable iterator not concurrently accessed.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL045", + "gate": "C3", + "title": "Exact add/subtract/multiply", + "verification": "Carry/borrow/sign/zero/large integer cases against independent exact expectations.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "Cartesian-add-order", + "exact-large-integer", + "exact-decimal-add", + "exact-borrow", + "exact-multiply" + ] + }, + { + "id": "CL046", + "gate": "C3", + "title": "Numeric admission and lexemes", + "verification": "Computed values normalized with no false originalLexeme; untouched inputs preserve spelling.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL047", + "gate": "C3", + "title": "Terminating exact division", + "verification": "Reduced denominators containing only2/5, large common factors and mixed signs.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "terminating-division" + ] + }, + { + "id": "CL048", + "gate": "C3", + "title": "Repeating division default", + "verification": "1/3 and repeating values give language inexactDivision; no floating fallback.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "repeating-division-error" + ] + }, + { + "id": "CL049", + "gate": "C3", + "title": "Explicit scale/half-even", + "verification": "Required option pair, decimal places, finite caps, signed ties and report rounding counts.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "rounded-third", + "half-even-ties", + "scale-pair-required" + ] + }, + { + "id": "CL050", + "gate": "C3", + "title": "Zero division and integral remainder", + "verification": "Typed catchable domain errors; signed truncated remainder; fractional remainder rejected.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "negative-remainder", + "fractional-remainder-rejected" + ] + }, + { + "id": "CL051", + "gate": "C3", + "title": "Exact numeric builtins", + "verification": "abs/floor/ceil/trunc/round/range/add with negative and empty/domain cases; round ties away from zero.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "round-away-from-zero", + "range-negative-step", + "range-zero-step" + ] + }, + { + "id": "CL052", + "gate": "C3", + "title": "Exponent/growth preflight", + "verification": "Large sparse exponent operations fail finite caps before dense allocation; exact zero shortcut allowed.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "exponent-gap-cap" + ] + }, + { + "id": "CL053", + "gate": "C3", + "title": "Numeric work and cancellation", + "verification": "Charged GCD/division/carry inner loops; boundaries and checkpoint cancellation; terminal errors not catchable.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL054", + "gate": "C3", + "title": "32-bit arithmetic safety", + "verification": "No Int/UInt64 negation/narrowing/product overflow traps; wasm32 kernel tests or explicit gate blocker.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL055", + "gate": "C3", + "title": "No Decimal/Double authority", + "verification": "Source audit plus precision cases prove no foreign arithmetic/fallback authority.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL056", + "gate": "C4", + "title": "Reduce/foreach streams", + "verification": "Last-update state, every foreach extraction, multiple inits, empty updates and actual evaluation order.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "reduce-last-update", + "reduce-empty-update", + "foreach-every-update", + "reduce-multiple-inits" + ] + }, + { + "id": "CL057", + "gate": "C4", + "title": "Lexical filter functions", + "verification": "Filter/value parameters, arity/scopes, self-recursion, no forward refs and finite explicit frames.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "filter-parameter-code", + "forward-definition-rejected", + "recursive-definition" + ] + }, + { + "id": "CL058", + "gate": "C4", + "title": "Loops/recurse/walk", + "verification": "Correct order/null handling/parent-after-child behavior with bounded frames/work and no fixed hidden10k cap.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "walk-child-before-parent", + "recurse-null-leaves" + ] + }, + { + "id": "CL059", + "gate": "C4", + "title": "Typed try/catch/optional", + "verification": "Catch only language errors; prior yields/handler streams correct; syntax/resource/cancel/IO never suppressed.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "input-eof-catch", + "malformed-input-cannot-catch", + "repeating-division-caught", + "division-zero-caught", + "try-prior-yield" + ] + }, + { + "id": "CL060", + "gate": "C4", + "title": "Collection builtin inventory", + "verification": "sort/group/unique/min/max/by forms, stable ties, multi-key generator lists, finite materialization.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "sort-exact-integers", + "total-order", + "group-scalar-distinct" + ] + }, + { + "id": "CL061", + "gate": "C4", + "title": "Generator first/last/nth", + "verification": "Correct pull/empty behavior and completion of remaining input; no hidden job early success.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "first-generator" + ] + }, + { + "id": "CL062", + "gate": "C4", + "title": "Flatten/any/all", + "verification": "Depth bounds, empty identities, short circuit and explicit deep traversal.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "all-any-empty" + ] + }, + { + "id": "CL063", + "gate": "C4", + "title": "Literal string helpers", + "verification": "Scalar contains/search/split/join/ASCII trim/case; empty patterns/type errors and bounded scan.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "split-empty-scalars", + "index-empty-pattern" + ] + }, + { + "id": "CL064", + "gate": "C4", + "title": "Conversions/explode/implode", + "verification": "Strict tonumber, preserved tostring, non-BMP scalar ints, invalid surrogate/range/type rejection.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "explode-non-BMP", + "implode-surrogate-rejected", + "tonumber-exact", + "tonumber-whitespace-rejected" + ] + }, + { + "id": "CL065", + "gate": "C4", + "title": "Path enumeration/admission", + "verification": "Exact segments, missing paths, generated paths, shape/domain restrictions and no Pointer conflation.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "setpath-created-array" + ] + }, + { + "id": "CL066", + "gate": "C4", + "title": "Assignment = versus |=", + "verification": "Original versus current-value RHS input; generator output, first update/empty delete semantics.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "assignment-set-generator", + "assignment-update-first", + "assignment-update-delete" + ] + }, + { + "id": "CL067", + "gate": "C4", + "title": "Transactional mutation/deletion", + "verification": "Stable array removals, deduplicated targets, overlap rejection and input unchanged on failure.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "assignment-overlap-rejected" + ] + }, + { + "id": "CL068", + "gate": "C4", + "title": "Sparse path growth", + "verification": "setpath container creation/null fill bounded before allocation and overflow.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "sparse-growth-fails" + ] + }, + { + "id": "CL069", + "gate": "C4", + "title": "String interpolation streams", + "verification": "Cartesian holes/literal identity/empty hole; formatter holes only with no eager unbounded strings.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "interpolation-generators", + "interpolation-empty" + ] + }, + { + "id": "CL070", + "gate": "C4", + "title": "Bounded format encoders", + "verification": "All C4 formatters, strict base64/UTF8, flat CSV/TSV, percent/HTML/POSIX quoting; no shell execution.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "format-html", + "format-base64" + ] + }, + { + "id": "CL071", + "gate": "C4", + "title": "Regex deferral", + "verification": "Compile-time unavailable diagnostics; no ICU/ECMA substitutions or compatibility claim.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "regex-not-advertised" + ] + }, + { + "id": "CL072", + "gate": "C4", + "title": "Other exclusions", + "verification": "Modules/seq/jq-stream/dates/env/halt/remote/server/converters rejected or absent from truthful help.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "numeric-transcendental-deferred", + "env-not-available" + ] + }, + { + "id": "CL073", + "gate": "C4", + "title": "Every inherited fixture disposition", + "verification": "119+49 originals retained, actual per-row status/reason/gate/claim, exclusions separated from successes.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL074", + "gate": "C5", + "title": "Stream completion commands", + "verification": "Check/cat/root-array reader, strict line/tail behavior and completed summary exactly once.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "stream-root-array", + "stream-root-array-bad-tail" + ] + }, + { + "id": "CL075", + "gate": "C5", + "title": "Static HTML rendering", + "verification": "JSONHTML integration with no script, expanded nodes, exact values/keys, escaping and finite shared accounting.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "render-static-inert" + ] + }, + { + "id": "CL076", + "gate": "C5", + "title": "Interactive HTML rendering", + "verification": "Approved fixed CSS/JS self-contained document with inert payloads; no server or external assets.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL077", + "gate": "C5", + "title": "Text/Markdown output", + "verification": "Tab pretty body, safe JSON fence and exact tokens; shared qualified writer options/defaults unchanged.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "render-markdown" + ] + }, + { + "id": "CL078", + "gate": "C5", + "title": "Offline schema/provider commands", + "verification": "Native schema check plus qualified local OpenAI helpers; schema object not quoted string, no network/key.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "schema-provider-object-envelope" + ] + }, + { + "id": "CL079", + "gate": "C5", + "title": "Provider incompatibility and availability", + "verification": "No constraint loss/lowering; compatibility false exit1; Apple signatures guarded and advertised only when qualified.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [ + "schema-provider-optionality-reject" + ] + }, + { + "id": "CL080", + "gate": "C5", + "title": "Native macOS executable qualification", + "verification": "Actual SDK27/Swift6.4 build/run/subprocess tests at final head.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL081", + "gate": "C5", + "title": "Linux x86_64 executable qualification", + "verification": "Actual Ubuntu24.04 x86_64 runtime and Linux/linux-safety receipts; not crosscompile-only.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL082", + "gate": "C5", + "title": "Linux ARM64 executable qualification", + "verification": "Actual Ubuntu24.04 ARM64 runtime and Linux/linux-safety receipts.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL083", + "gate": "C5", + "title": "Mobile library compatibility", + "verification": "Selected-product iOS/iPadOS consumers and existing mobile qualification preserved; no CLI process support claim.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL084", + "gate": "C5", + "title": "WASI CLI disposition", + "verification": "Explicit final supported/blocked/limited evidence with resources/argv/preopen/counters; no inherited CLI pass.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL085", + "gate": "C5", + "title": "Baseline WASI/core preservation", + "verification": "Existing all-four-product resource/integrity/49metrics/platform lanes retain exact truthful evidence.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL086", + "gate": "C5", + "title": "Installed resources/clean extraction", + "verification": "Source archive rebuilt, executable run outside repo cwd, sibling schema bundle and missing/tampered resource tests.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL087", + "gate": "C5", + "title": "Fresh oracle and comparisons", + "verification": "Pinned jq1.8.2 build/hash; native exact comparison plus byte/exit modes; missing oracle blocks that lane.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL088", + "gate": "C5", + "title": "Independent exact expectations", + "verification": "Integer/rational/Unicode/cardinality expectations separate from native self-comparison and jq floats.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL089", + "gate": "C5", + "title": "Streaming retention/performance", + "verification": "Generated chunks/slow sink/large record counts and bounded collectors; release-mode identities/variance, no blanket speedup.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL090", + "gate": "C5", + "title": "Six-product CI/documentation", + "verification": "Existing native/safety/docs plus affected Linux lanes, exact product kinds and public CLI help/docs/examples.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL091", + "gate": "C5", + "title": "Final-head review/return evidence", + "verification": "Actual PR/base/head topology, nonzero counts, immutable IDs/log hashes/fixture results, blockers and release recommendation.", + "obligation": "mandatory", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL092", + "gate": "C5", + "title": "Optional parallel evaluator", + "verification": "Only independent record filters; reserved bounded tasks/reorder buffers, preserved order/errors and measured material gain.", + "obligation": "optional-extension", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL093", + "gate": "C5", + "title": "Optional Apple PDF/RTF CLI export", + "verification": "Only after existing export backend qualification; actual host, explicit async preparation/page capabilities and unsupported-host errors.", + "obligation": "optional-extension", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + }, + { + "id": "CL094", + "gate": "C5", + "title": "Optional native binary distribution", + "verification": "Explicit packaging policy, architecture/resource/signature receipts; no publication/tag performed by this task.", + "obligation": "optional-extension", + "status": "not_run", + "evidence_required": true, + "fixture_ids": [] + } + ], + "notes": [ + "Package integrity is separate from native qualification.", + "Optional extensions do not block the scoped candidate when not claimed.", + "WASI CLI supported/blocked/limited disposition is mandatory; full CLI support is separately earned." + ] +} diff --git a/Documentation/Architecture/Release1.4/Foundation.md b/Documentation/Architecture/Release1.4/Foundation.md new file mode 100644 index 0000000..9d5f7ac --- /dev/null +++ b/Documentation/Architecture/Release1.4/Foundation.md @@ -0,0 +1,103 @@ +# CLI foundation + +This feature boundary implements format, strict validation, RFC 6901 selection, +staged RFC 6902 application and HTML/text/Markdown rendering. It does not advertise +the query engine, stream/schema command adapters or provider helpers before their +own increments are qualified. The target release is 1.4.0; this is development source. + +## Composition and ownership + +`CLIJobContext: ~Copyable` lives in the private runtime target. Argv, native parsing, +number projection, registry/plan admission, validation, Patch, rendering and writing +borrow its one `JSONWorkMeter`. Package-only borrowed entry points introduce no new +public library signatures. Switching the operation domain changes subsystem work +accounting without replacing health or counters. Only per-value shape capacities +reset; consumed work never resets. Patch refunds its local staging reservation and +preserves ownership that preceded its invocation. + +CLI policy has 16 additional finite quotas. Core policy keeps exactly 49 metric +identifiers. Effective work and logical-retention ceilings are the intersection of +CLI and core ceilings. Configuration bootstraps under the finite offline profile, +then validates requested caps against already consumed work and admitted capacities. +Explicit finite overrides reuse the ledger. They cannot erase bootstrap input. + +Error/cancellation latches terminal health. Data paths cannot resume after failure. +Diagnostics reserve finite work and buffer ownership before command execution; +the bounded terminal serializer spends that reservation and cannot reopen the job. +Reports distinguish provisional results from fully accepted stdout results and +atomically installed file results. Logical retained bytes are not process RSS. + +## Native I/O + +The adapters poll nonblocking POSIX descriptors, check cancellation between polls, +and handle short reads/writes, EINTR, EAGAIN, EOF and broken pipes explicitly. +Filesystem/kernel work is foreign work; cancellation does not claim to interrupt +a filesystem operation already executing in the kernel. Borrowed stdio flags are +restored. The signal callback is explicitly `@Sendable`; a real subprocess test +caught and fixed an inferred-actor dispatch assertion on SIGINT. + +File output uses an exclusive owned temporary in the destination directory. +Installation happens after parsing, command evaluation, writing and close succeed. +No-overwrite installation uses link/unlink; explicit overwrite uses rename. +Existing destinations, final symlinks and input aliases/hard links are checked. +Failures remove only the invocation's temporary and preserve the destination. +Concurrent pathname replacement by another process is outside this adapter's +transactional guarantee. There is no cross-filesystem fallback, mkdir, fsync or +power-loss durability claim. Previously accepted stdout bytes cannot be recalled. + +## Checks + +Twelve meaningful Swift Testing functions include four parameter cases. The executable +subprocess harness has 53 cases covering exact lexemes/scalar keys, strict errors, +schema invalidity versus operational failure, bounded issues, Pointer null/absence, +root removal, failed Patch staging, renderer selection, limits/reports, filesystem +aliases, atomic cleanup and SIGINT while stdin is idle. The initial failure receipts +are retained; the corrected subprocess lane passes all 53. + +The event receipt reader is reused from the separately qualified 1.3 export work +at `1e0b86d2580bb9678039597523aa3b99d2905abf`. No export/PDF feature source is copied. +The six-product checks retain exact target/dependency/toolchain/platform assertions, +all baseline test bundle counts, 173 inherited parameter cases plus four CLI cases, +and all 49 cap probe outcomes. Native/safety/docs qualification remains required +for the committed source. + +## Compiler probes and review corrections + +The supported native compiler accepts an inout job borrow, rejects consuming the +same job twice with the expected ownership diagnostic, and rejects access from an +external package. The noncopyable initializer constructs its meter before the +other stored assignments; this follows actual Swift 6.4 initialization rules. +An initial default-CLT/cache attempt is unqualified and retained as blocked evidence; +its negative exits are not ownership passes. + +Strict memory-safety compilation marks individual synchronous POSIX pointer and +CString expressions `unsafe`. Byte pointers stay within `withUnsafeBytes` scopes; +counts are checked against the array slice, poll/stat pointers address live local +structures, and C strings are borrowed only for a synchronous syscall. No pointer +escapes, suspension crosses a borrow, unsafe compiler option or blanket annotation +is introduced. + +The SIGINT acceptance test waits for its exclusive staging file as a readiness +signal and holds stdin open until exit. Closing stdin through `communicate()` +immediately after SIGINT introduced a competing EOF and was corrected. The test +still requires exit 130, no output, and complete staging cleanup. + +The final argument adapter reads process-owned C argv under the finite bootstrap +ledger, rejects invalid UTF-8 instead of accepting replacement characters, and +checks each byte before copying. Structured diagnostic preferences survive usage +errors. Malformed/overflowing limit configuration is usage failure; exhaustion, +cancellation and I/O remain operational. Missing array Pointer targets use a +package-only presence adapter over the shared strict index decoder; malformed +indices and wrong kinds stay errors. Existing public Pointer resolution retains +its previous error contract. These review findings have dedicated regressions. + +Named inputs open with O_NONBLOCK from the initial syscall, so FIFO open does not +wait indefinitely before cancellation handling begins. Subprocess cancellation +covers both idle borrowed stdin and an owned named FIFO. Reports include actual +enforced native caps, remaining cumulative work and profile/override origin. + +The terminal diagnostic builder checks each append before growth and discards an +incomplete report before handoff. Zero forbids diagnostic output; small diagnostic +quotas cannot produce broken JSONL. HTML-specific quotas map to resource exit 6; +renderer configuration maps to usage exit 2. Typed schema compile/evaluation errors +stay operational exit 5, with stable subsystem codes. These have explicit regressions. diff --git a/Documentation/Architecture/Release1.4/Plan.md b/Documentation/Architecture/Release1.4/Plan.md new file mode 100644 index 0000000..9ae86d6 --- /dev/null +++ b/Documentation/Architecture/Release1.4/Plan.md @@ -0,0 +1,40 @@ +# CLI release plan + +The maintainer-approved 1.4.0 train adds `swift-json` as an executable beside the +five libraries. JSONFilterRuntime and JSONCLIEngine are package-private targets; +JSONRegexRuntime remains private. Root external dependencies remain empty. +Historical four/five-product releases and their evidence are unchanged. + +The CLI stack starts at the qualified JSONHTML renderer commit +`3c1e8a1a1bc8d03699665b8ab98f5ece7962aff7`, PR #21. Its native, safety, docs and +Linux x86_64/arm64 lanes passed. Main at orientation was +`25fb04f747e583209b4c4511abf39e11ef5b4f80`. This train uses dependent feature +branches and draft PRs; it does not change the parallel transfer/PDF or 1.2 work. + +| Gate | Cohesive implementation and acceptance | +| --- | --- | +| C0 | Source/protection/prerequisite audit, executable/private target shapes, one noncopyable job ledger | +| C1 | Bounded argv/stdio/files, diagnostics/exits, atomic output, format/validate/Pointer/Patch, six-product inventory | +| C2 | Original flat filter IR and pull VM, input framing, generators/paths/control/bindings/common builtins | +| C3 | Private exact decimal arithmetic; explicit scale plus half-even division policy | +| C4 | Qualified richer filters, lexical functions/reductions/path updates; explicit unsupported features | +| C5 | Qualified renderer/schema/provider adapters, consumers, independent corpus/oracle, platform/distribution and final candidate | + +The planned stack is foundation, query-core, arithmetic, richer filters and +integration. Each has a compiling boundary, exact acceptance receipts and a draft +PR against its actual dependency. No public query library, eager legacy evaluator, +remote resolver, shell executor, daemon or release action is introduced. + +The input briefing SHA-256 is +`43f8819c57e3d93bc6f7cf561d2d111442e089f2973cb7c62e16e3bd3fd73d0d`. +Its structural verifier counted 94 requirements, 111 new cases and 168 inherited +cases; those are inventory counts, not execution successes. The jq 1.7 observations +are historical references; differential claims require an actual pinned jq 1.8.2 +runner. Missing provider APIs/platform runners remain explicit blockers. + +Swift 6.4, language 6 and macOS/SDK 27 remain the native baseline. Mobile libraries +must continue to compile independently of the executable. Linux is a process +host; WASI executable feasibility is separate from the already qualified libraries. +Native and sanitizer receipts, subprocess outcomes, commands, toolchain, source and +binary identities accompany every qualified milestone. All 49 baseline probes and +independent-suite exclusions remain required and separate from CLI additions. diff --git a/Documentation/Architecture/Release1.4/Progress.json b/Documentation/Architecture/Release1.4/Progress.json new file mode 100644 index 0000000..176a9e8 --- /dev/null +++ b/Documentation/Architecture/Release1.4/Progress.json @@ -0,0 +1,41 @@ +{ + "schema_version": 1, + "target_release": "1.4.0", + "renderer_base": "3c1e8a1a1bc8d03699665b8ab98f5ece7962aff7", + "main_at_orientation": "25fb04f747e583209b4c4511abf39e11ef5b4f80", + "branch": "codex/1.4-json-cli-foundation", + "status": "Compiling foundation boundary qualified on native macOS; C0/C1 complete closure and C2-C5 remain pending", + "gates": { + "C0": "partial", + "C1": "partial", + "C2": "not_run", + "C3": "not_run", + "C4": "not_run", + "C5": "not_run" + }, + "foundation_functions": 12, + "foundation_parameter_cases": 4, + "subprocess_cases": 53, + "subprocess_passed": 53, + "baseline_parameter_cases": 173, + "core_metrics": 49, + "cli_metrics": 16, + "publication_performed": false, + "root_test_functions": 232, + "root_parameter_cases": 177, + "native_source_manifest_sha256": "864d9b3dd8e1d79a0a6cbec862476843ddca2ff8a1582471b80fba54b863459e", + "qualified_lanes": [ + "native", + "safety", + "docs" + ], + "remaining_foundation_requirements": [ + "clean-source reproduction", + "selected mobile SDK consumers", + "immutable filter-plan compiler probe and implemented IR", + "all 16 CLI cap boundaries", + "multi-record shared accounting", + "slow/partial/no-progress sink probes", + "query compile exit 3 and full exit precedence" + ] +} diff --git a/Documentation/Architecture/Release1.4/Qualification.md b/Documentation/Architecture/Release1.4/Qualification.md new file mode 100644 index 0000000..460214c --- /dev/null +++ b/Documentation/Architecture/Release1.4/Qualification.md @@ -0,0 +1,50 @@ +# Foundation qualification boundary + +The foundation code and harness were qualified together under source manifest +`864d9b3dd8e1d79a0a6cbec862476843ddca2ff8a1582471b80fba54b863459e`. +The receipts identify the renderer base HEAD plus the actual edited-file hashes; +this is pre-commit evidence, not a claim that the parent commit contains the CLI. +Only this ledger, progress and qualification prose were subsequently updated. +Production Swift, the package manifest and executed scripts match those receipts. + +Native environment: arm64 macOS 27.0; Swift 6.4 +(swiftlang-6.4.0.34.1), language mode 6, Xcode 27.2 (27B5019j), SDK 27.2. +The configured beta Xcode is required on this host; the default CLT attempt was +unqualified and is retained separately. No baseline or compiler check was lowered. + +| Command | Actual outcome | +| --- | --- | +| `python3 Scripts/ci.py --lane native --output .build/ci/cli-c1-reviewed-native` | Exit 0; all 18 commands passed; debug/release each 232 functions, 177 parameter invocations | +| `python3 Scripts/ci.py --lane safety --output .build/ci/cli-c1-reviewed-safety` | Exit 0; ASan 232 functions/177 parameter invocations; TSan seven shared-state functions; strict-memory-safety build with warnings as errors | +| `python3 Scripts/ci.py --lane docs --output .build/ci/cli-c1-reviewed-docs` | Exit 0; five DocC catalogues, coverage checks and compiled examples | +| `python3 Scripts/qualify_cli.py --binary .build/release/swift-json --output …` | Exit 0; 53 real subprocess cases, no exclusions | +| `python3 Scripts/qualify_cli_ownership.py --output …` | Exit 0; three compiler probes: borrow compiles; double consume and cross-package access fail with required diagnostics | + +The inherited 173 parameter cases remain unchanged; four CLI parameter cases are +additional. All 49 baseline cap probes keep their recorded outcomes. Independent +syntax qualification observes 318 cases: 287 evaluated successes and 31 policy +cases. Schema qualification observes 2,337: 2,300 evaluated successes and 37 policy +cases. Authored suites retain 82 public semantics, nine budget/counter cases and +4,396 deterministic malformed-input replays. The downstream consumer retains 20 +explicitly rooted outcomes: eight positive, twelve negative, with two operational +error outcomes distinguished from ordinary invalidity. These do not establish MCP +server conformance. + +The three final lane receipts report unchanged source inputs while running. Earlier +failed compiler, SIGINT actor-isolation, EOF-race, normalized-spelling expectation +and strict-pointer-annotation attempts are retained alongside successful corrections. +The expected normalized `1e-1` spelling follows the existing coefficient/exponent +contract; no existing test was weakened. + +This is a reviewable foundation milestone. All 94 acceptance IDs remain in +Acceptance.json. Partial/not_run rows are not successes: complete filter IR/VM, +arithmetic/richer filters, all 16 CLI cap boundaries, split/slow/no-progress I/O, +selected mobile/portable consumers and clean candidate reproduction remain pending. +The renderer prerequisite is PR #21 at `3c1e8a1`; transfer/PDF work and the parallel +1.2 train are unchanged. Provider bridge APIs are absent at this base; qualification +and integration must precede their advertisement. No tag, merge or publication is +performed by this boundary. + +Materialized receipts and their exact source-input records are in the local +foundation evidence bundle. Run the listed commands to regenerate evidence; local +build outputs and private input packets are excluded from public Git/source archives. diff --git a/Documentation/README.md b/Documentation/README.md index 44892ae..9e022d6 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -33,6 +33,8 @@ Compiled archives and public symbol graphs remain available in CI artifacts. - [Linux platform and portable schema integrity](Architecture/Linux.md) - [WebAssembly audit and unresolved qualification boundary](Architecture/WebAssemblyAudit.md) +- [Prospective 1.4 CLI plan](Architecture/Release1.4/Plan.md) and [foundation contract](Architecture/Release1.4/Foundation.md) + ## Consumers, qualification and maintenance - [Five-product compiled example](../Examples/Consumer/README.md) diff --git a/Package.swift b/Package.swift index 4fbde36..c68db49 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,8 @@ let package = Package( .library(name: "JSONSchema", targets: ["JSONSchema"]), .library(name: "JSONStreaming", targets: ["JSONStreaming"]), .library(name: "JSONPatch", targets: ["JSONPatch"]), - .library(name: "JSONHTML", targets: ["JSONHTML"]) + .library(name: "JSONHTML", targets: ["JSONHTML"]), + .executable(name: "swift-json", targets: ["JSONCLI"]) ], targets: [ .target(name: "JSON"), @@ -19,6 +20,10 @@ let package = Package( .target(name: "JSONStreaming", dependencies: ["JSON"]), .target(name: "JSONPatch", dependencies: ["JSON"]), .target(name: "JSONHTML", dependencies: ["JSON"], exclude: ["Assets"]), + .target(name: "JSONFilterRuntime", dependencies: ["JSON"]), + .target(name: "JSONCLIEngine", dependencies: ["JSON", "JSONSchema", "JSONStreaming", "JSONPatch", "JSONHTML", "JSONFilterRuntime"]), + .executableTarget(name: "JSONCLI", dependencies: ["JSONCLIEngine"]), + .testTarget(name: "JSONCLITests", dependencies: ["JSONCLIEngine", "JSONFilterRuntime", "JSON", "JSONPatch", "JSONSchema", "JSONHTML"]), .testTarget(name: "JSONHTMLTests", dependencies: ["JSONHTML", "JSON"]), .testTarget(name: "JSONTests", dependencies: ["JSON"], exclude: ["ImplementationPlan.md"]), .testTarget(name: "JSONSchemaTests", dependencies: ["JSONSchema"], exclude: ["ImplementationPlan.md"]), diff --git a/README.md b/README.md index 7ab8d4e..a6a0826 100644 --- a/README.md +++ b/README.md @@ -474,3 +474,19 @@ following [SECURITY](SECURITY.md); see [SUPPORT](SUPPORT.md) for maintenance and Original code is licensed under [Apache-2.0](LICENSE). Adapted code and qualification inputs retain their applicable [notices](NOTICE), [third-party notices](THIRD_PARTY_NOTICES.md), [licenses](Licenses) and [extraction provenance](Documentation/Architecture/ExtractionManifest.json). + +## Prospective 1.4 command-line tool + +This feature train adds `swift-json` beside the five libraries. Its compiling +foundation provides strict formatting, validation, Pointer selection, staged +Patch and qualified HTML/text/Markdown rendering. See the [CLI plan](Documentation/Architecture/Release1.4/Plan.md) +and [foundation contract](Documentation/Architecture/Release1.4/Foundation.md) +for command semantics, cumulative budgets and qualification boundaries. Query and +provider commands are advertised only after their separate gates pass. + +```sh +swift build -c release --product swift-json +.build/release/swift-json format --compact input.json +.build/release/swift-json validate --schema schema.json input.json +.build/release/swift-json render input.json --output inspection.html +``` diff --git a/Scripts/ci.py b/Scripts/ci.py index 6ed3220..5108e93 100644 --- a/Scripts/ci.py +++ b/Scripts/ci.py @@ -25,7 +25,7 @@ def commands(lane, output): # Root behavior/corpora remain required. SwiftData and Apple SDK/lifetime # probes are separate Apple acceptance; they never become Linux passes. return [c for c in commands('native', ROOT / output) - if not any(x in c for x in ['Tools/MobileQualification', '--mode'])] + [ + if not any(x in c for x in ['Tools/MobileQualification', '--mode', 'Scripts/qualify_cli_ownership.py'])] + [ ['python3', '-B', 'Scripts/qualify_linux_reproduction.py', '--output', str(output / 'reproduction')]] if lane == 'linux-safety': # Every behavior case uses Swift Testing. Do not start the generated @@ -53,6 +53,8 @@ def commands(lane, output): ['swift', 'run', '-c', 'release', '--package-path', 'Tools/JSONHTMLBrowser', 'BrowserFixtures', str(output / 'fixtures')], ['python3', '-B', 'Scripts/qualify_html_browser.py', '--fixtures', str(output / 'fixtures'), '--output', str(output / 'browser')], ] + if lane == 'cli': + return [['swift', 'build', '-c', 'release', '--product', 'swift-json'], ['swift', 'test', '--filter', 'cli'], ['python3', '-B', 'Scripts/qualify_cli.py', '--binary', '.build/release/swift-json', '--output', str(output / 'subprocess.json')]] if lane == 'native': return [ ['python3', '-B', 'Scripts/public_check.py'], @@ -61,6 +63,8 @@ def commands(lane, output): ['swift', 'build', '-c', 'release'], ['python3', '-B', 'Scripts/verify_testing_events.py'], ['swift', 'test'], ['swift', 'test', '-c', 'release'], + ['python3', '-B', 'Scripts/qualify_cli.py', '--binary', '.build/release/swift-json', '--output', str(output / 'subprocess.json')], + ['python3', '-B', 'Scripts/qualify_cli_ownership.py', '--output', str(output / 'cli-ownership')], ['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'], @@ -112,10 +116,12 @@ def check_result(command, code, log, events=None): if events is not None: counts = events['bundle_counts'] if command[:2] == ['swift', 'test']: - expected = [38] if 'export|Export|apple' in command else [14] if 'Tools/WasmAudit' in command else [2, 3, 3, 11] if 'qa' in command else [7] if R0_TESTS in command else [3] if 'Tools/MCPReadiness' in command or 'Tools/MobileQualification' in command else [1] if 'Tools/Qualification' in command else [1, 1, 3 if sys.platform == 'linux' else 4, 4] if 'thread' in command else sorted([11, 12, 14, 61 if sys.platform == 'linux' else 75, 51, 107]) + expected = [12] if 'cli' in command else [38] if 'export|Export|apple' in command else [14] if 'Tools/WasmAudit' in command else [2, 3, 3, 11] if 'qa' in command else [7] if R0_TESTS in command else [3] if 'Tools/MCPReadiness' in command or 'Tools/MobileQualification' in command else [1] if 'Tools/Qualification' in command else [1, 1, 3 if sys.platform == 'linux' else 4, 4] if 'thread' in command else sorted([11, 12, 12, 14, 61 if sys.platform == 'linux' else 75, 51, 107]) passed = passed and sorted(counts) == expected if '--filter' not in command and not any(x.startswith('Tools/') for x in command): - passed = passed and (events['parameterized_functions'] == ROOT_PARAMETER_CASES if events is not None else sum(int(n) for n in re.findall(r'with (\d+) test cases passed', log)) == 173) + if events is not None: + passed = passed and events['module_function_counts'] == {'JSONTests':107,'JSONSchemaTests':51,'JSONStreamingTests':14,'JSONPatchTests':11,'JSONRegexRuntimeTests':12,'JSONHTMLTests':61 if sys.platform == 'linux' else 75,'JSONCLITests':12} + 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)) == 177) if 'Tools/Qualification' in command: probes = re.findall(r'^CAP_PROBE (\w+) (\S+)$', log, re.MULTILINE) matrix = json.loads((ROOT / 'Documentation/Qualification/LimitsMatrix.json').read_text()) @@ -123,10 +129,10 @@ def check_result(command, code, log, events=None): passed = passed and len(probes) == 49 and dict(probes) == expected_probes if command == ['swift', 'package', 'dump-package'] and passed: d = json.loads(log) - passed = sorted(p['name'] for p in d['products']) == ['JSON', 'JSONHTML', 'JSONPatch', 'JSONSchema', 'JSONStreaming'] 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']} == {name: [name] for name in ['JSON', 'JSONSchema', 'JSONStreaming', 'JSONPatch', 'JSONHTML']} + passed = sorted(p['name'] for p in d['products']) == ['JSON', 'JSONHTML', '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']}, **{'swift-json': ['JSONCLI']}) targets = {t['name']: t for t in d['targets']} - passed = passed and set(targets) == {'JSON', 'JSONSchema', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONRegexRuntime', 'JSONTests', 'JSONSchemaTests', 'JSONStreamingTests', 'JSONPatchTests', 'JSONHTMLTests', 'JSONRegexRuntimeTests'} and targets['JSONHTML']['dependencies'] == [{'byName': ['JSON', None]}] + passed = passed and set(targets) == {'JSON', 'JSONSchema', 'JSONStreaming', 'JSONPatch', 'JSONHTML', 'JSONRegexRuntime', 'JSONTests', 'JSONSchemaTests', 'JSONStreamingTests', 'JSONPatchTests', 'JSONHTMLTests', 'JSONRegexRuntimeTests', 'JSONFilterRuntime', 'JSONCLIEngine', 'JSONCLI', 'JSONCLITests'} and targets['JSONHTML']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONFilterRuntime']['dependencies'] == [{'byName': ['JSON', None]}] and targets['JSONCLI']['type'] == 'executable' if command == ['swift', 'package', 'show-dependencies', '--format', 'json'] and passed: passed = json.loads(log)['dependencies'] == [] if command[:2] == ['swift', 'run'] and 'Tools/MobileQualification' in command: @@ -143,7 +149,7 @@ def check_result(command, code, log, events=None): def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--lane', choices=['native', 'safety', 'docs', 'r0', 'qa', 'linux', 'linux-safety', 'wasm', 'browser', 'export'], required=True) + parser.add_argument('--lane', choices=['native', 'safety', 'docs', 'r0', 'qa', 'linux', 'linux-safety', 'wasm', 'browser', 'export', 'cli'], required=True) parser.add_argument('--output') args = parser.parse_args() output = ROOT / (args.output or '.build/ci/' + args.lane) diff --git a/Scripts/qualify_cli.py b/Scripts/qualify_cli.py new file mode 100644 index 0000000..8ce7f81 --- /dev/null +++ b/Scripts/qualify_cli.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Exercise the real CLI subprocess, exact bytes, exits, stderr and atomic files.""" +import argparse, hashlib, json, os, pathlib, signal, subprocess, tempfile, time + +ROOT = pathlib.Path(__file__).resolve().parents[1] + +def main(): + parser = argparse.ArgumentParser(); parser.add_argument('--binary', required=True); parser.add_argument('--output', required=True) + args = parser.parse_args(); binary = (ROOT / args.binary).resolve(); rows = [] + def run(name, argv, data=b'', exit=0, expected=None, check=None): + command = [str(binary), *argv]; process = subprocess.run(command, input=data, capture_output=True, timeout=30) + ok = process.returncode == exit and (expected is None or process.stdout == expected) + if check: ok = ok and check(process) + rows.append({'id': name, 'command': ['swift-json', *[a if isinstance(a,str) else {'bytes_hex':a.hex()} for a in argv]], 'exit_code': process.returncode, 'expected_exit': exit, + 'stdout_sha256': hashlib.sha256(process.stdout).hexdigest(), 'stdout_bytes': len(process.stdout), + 'stderr_sha256': hashlib.sha256(process.stderr).hexdigest(), 'stderr_bytes': len(process.stderr), 'status': 'passed' if ok else 'failed'}) + if not ok: print(name, process.returncode, repr(process.stdout[:250]), repr(process.stderr[:500]), flush=True) + return process + with tempfile.TemporaryDirectory(prefix='swift-json-cli-') as temp: + temp = pathlib.Path(temp) + def file(name, data): + path = temp / name; path.write_bytes(data); return str(path) + def limit(**caps): return file('limits.json', json.dumps({'version':1,'cli':caps}).encode()) + run('help', ['--help'], check=lambda p: b'format' in p.stdout and not p.stderr) + run('invalid-argv-utf8', ['format', b'\xff'], exit=2, expected=b'', check=lambda p: b'invalidArgvUTF8' in p.stderr) + run('version', ['--version'], expected=b'swift-json 1.4.0-dev\n') + run('version-json', ['--version','--json'], check=lambda p: json.loads(p.stdout)['revision'] == 'unknown') + run('missing-command', [], exit=2, expected=b'') + run('unknown-option', ['format','--unknown'], exit=2, expected=b'') + run('usage-json-diagnostic', ['format','--diagnostics=json','--unknown'], exit=2, expected=b'', check=lambda p: json.loads(p.stderr)['kind'] == 'diagnostic' and json.loads(p.stderr)['category'] == 'usage') + run('duplicate-option', ['format','--compact','--compact'], exit=2, expected=b'') + run('indent-conflict', ['format','--compact','--tab'], exit=2, expected=b'') + run('preserved-numbers', ['format','--compact'], b'[9007199254740993,0.10,-0,1e1000]', expected=b'[9007199254740993,0.10,-0,1e1000]\n') + run('normalized-numbers', ['format','--compact','--number-style=normalized'], b'[0.10,-0]', expected=b'[1e-1,0]\n') + run('tab-indentation', ['format','--tab'], b'{"a":[1]}', expected=b'{\n\t"a": [\n\t\t1\n\t]\n}\n') + scalar = '{"é":1,"é":2}'.encode() + run('scalar-distinct-keys', ['format','-cS'], scalar, expected='{"é":2,"é":1}\n'.encode()) + run('duplicate-decoded-key', ['format','--compact'], b'{"a":1,"\\u0061":2}', exit=5, expected=b'') + run('malformed-utf8', ['format'], b'"\xff"', exit=5, expected=b'') + run('trailing-root', ['format'], b'{} []', exit=5, expected=b'') + run('validation-syntax', ['validate'], b'{}', check=lambda p: json.loads(p.stdout) == {'valid':True,'issues':[],'truncated':False}) + run('validation-malformed', ['validate'], b'{', exit=5, expected=b'') + schema = file('schema.json', b'{"type":"integer","minimum":3}') + run('schema-valid', ['validate','--schema',schema], b'4', check=lambda p: json.loads(p.stdout)['valid'] is True) + run('schema-invalid', ['validate','--schema',schema], b'2', exit=1, check=lambda p: json.loads(p.stdout)['valid'] is False and len(json.loads(p.stdout)['issues']) > 0) + run('schema-bounded-issues', ['validate','--schema',schema,'--max-issues','0'], b'2', exit=1, check=lambda p: json.loads(p.stdout) == {'valid':False,'issues':[],'truncated':True}) + run('pointer-null', ['pointer','/a'], b'{"a":null}', expected=b'null\n') + run('pointer-missing', ['pointer','/a'], b'{}', exit=5, expected=b'') + run('pointer-allow-missing', ['pointer','/a','--allow-missing'], b'{}', exit=4, expected=b'') + run('pointer-array-absence', ['pointer','/0','--allow-missing'], b'[]', exit=4, expected=b'') + run('pointer-malformed-index-stays-error', ['pointer','/01','--allow-missing'], b'[]', exit=5, expected=b'') + run('pointer-wrong-type', ['pointer','/a','--allow-missing'], b'[]', exit=5, expected=b'') + run('pointer-escaped-key', ['pointer','/a~1b~0c'], b'{"a/b~c":1}', expected=b'1\n') + patch = file('patch.json', b'[{"op":"remove","path":""}]') + run('patch-root-absence', ['patch','--patch',patch], b'null', expected=b'') + patch = file('patch.json', b'[{"op":"add","path":"/b","value":9007199254740993}]') + run('patch-exact-value', ['patch','--patch',patch], b'{"a":1}', check=lambda p: b'9007199254740993' in p.stdout) + patch = file('patch.json', b'[{"op":"add","path":"/b","value":2},{"op":"test","path":"/a","value":0}]') + run('patch-failure-no-candidate', ['patch','--patch',patch], b'{"a":1}', exit=5, expected=b'') + run('html-invalid-title-is-usage', ['render','--title','\x01'], b'{}', exit=2, expected=b'') + run('static-html', ['render'], b'{"x":"\",\"n\":9007199254740993}\n", + "expected_exit": 0, + "comparison": "process-contract", + "status": "not_run", + "probe": { + "assert_no_script_element": true, + "assert_exact_value": true, + "assert_no_external_assets": true + } + }, + { + "id": "render-markdown", + "gate": "C5", + "requirement_ids": [ + "CL077" + ], + "command": "render", + "options": [ + "--format", + "markdown" + ], + "operands": [], + "stdin_utf8": "{\"a\":[1.2300e+2]}\n", + "expected_exit": 0, + "comparison": "stdout-byte-exact", + "status": "not_run", + "expected_stdout_utf8": "```json\n{\n\t\"a\": [\n\t\t1.2300e+2\n\t]\n}\n```\n" + } + ] +} diff --git a/Tests/JSONCLITests/QueryBudgetTests.swift b/Tests/JSONCLITests/QueryBudgetTests.swift new file mode 100644 index 0000000..e7e6e2f --- /dev/null +++ b/Tests/JSONCLITests/QueryBudgetTests.swift @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +import Testing +import JSON +@testable import JSONFilterRuntime + +@Test(arguments: CLIResourceMetric.allCases) +func cliEveryCounterHasInclusiveCheckedBoundaryAndTerminalFailure(metric: CLIResourceMetric) throws { + var limits = CLILimits(); limits[metric] = 3 + var context = try CLIJobContext(limits: limits) + try context.charge(metric, 3) + #expect(context.used(metric) == 3) + do { try context.charge(metric); Issue.record("Expected cap+1 rejection") } + catch let error as CLIError { #expect(error.category == .resource && error.metric == metric.rawValue) } + #expect(throws: CLIError.self) { try context.checkpoint() } +} +@Test(arguments: [CLIResourceMetric.querySourceBytes, .queryTokens, .astNodes, .queryDepth]) +func cliCompilerEnforcesCapsAtActualSourceTokenAndIRGrowth(metric: CLIResourceMetric) throws { + var limits = CLILimits(); limits[metric] = 1 + var allowed = try CLIJobContext(limits: limits) + let program = try FilterCompiler.compile(".", context: &allowed) + #expect(program.nodes.count == 1) + limits[metric] = 0 + var blocked = try CLIJobContext(limits: limits) + do { _ = try FilterCompiler.compile(".", context: &blocked); Issue.record("Expected actual compiler admission failure") } + catch let failure as CLIError { #expect(failure.category == .resource && failure.metric == metric.rawValue) } +} +@Test func cliParserDepthUsesConfiguredWorkStackAndRejectsWithoutRecursion() throws { + var limits = CLILimits(); limits[.queryDepth] = 64 + var context = try CLIJobContext(limits: limits) + let source = String(repeating: "(", count: 65) + "." + String(repeating: ")", count: 65) + do { _ = try FilterCompiler.compile(source, context: &context); Issue.record("Expected depth cap") } + catch let failure as CLIError { #expect(failure.metric == "queryDepth") } +} +@Test func cliRuntimeFramesAndBindingsAreLiveOwnedCaps() throws { + var limits = CLILimits(); limits[.runtimeFrames] = 0 + var context = try CLIJobContext(limits: limits) + let program = try FilterCompiler.compile(".", context: &context) + do { _ = try FilterVM(program: program, input: .init(value: .null), context: &context); Issue.record("Expected frame cap before growth") } + catch let failure as CLIError { #expect(failure.metric == "runtimeFrames") } +} +@Test func cliQueryPlanIsImmutableAndCheckedSendable() throws { + func requireSendable(_ value: T) {} + var context = try CLIJobContext() + let program = try FilterCompiler.compile(".a, .b", context: &context) + requireSendable(program) + #expect(program.nodes.count > 1 && program.sourceRanges.count == program.nodes.count) +} + +@Test func cliQueryOperationalFailureBypassesTryAndCleansInvocationStorage() throws { + var compilation = try CLIJobContext() + let program = try FilterCompiler.compile("try [1,2,3] catch null", context: &compilation) + var limits = CLILimits(); limits[.collectionValues] = 2 + var context = try CLIJobContext(limits: limits) + let baseline = context.meter.used(.retainedBytes) + var vm = try FilterVM(program: program, input: .init(value: .null), context: &context) + struct NoInput: FilterInput { mutating func next(context: inout CLIJobContext) throws -> FilterYield? { nil } } + var input = NoInput() + do { _ = try vm.next(input: &input, context: &context); Issue.record("Resource failure must not become catch null") } + catch let failure as CLIError { #expect(failure.category == .resource && failure.metric == "collectionValues") } + try vm.close(context: &context) + #expect(context.used(.runtimeFrames) == 0 && context.used(.collectionValues) == 0) + #expect(context.meter.used(.retainedBytes) == baseline) + #expect(throws: CLIError.self) { try context.checkpoint() } +} +@Test func cliQueryCancellationHappensInsideOwnedEvaluationAndCleansFrames() async throws { + var compilation = try CLIJobContext() + let program = try FilterCompiler.compile(".[]", context: &compilation) + let task = Task { + var context = try CLIJobContext() + let baseline = context.meter.used(.retainedBytes) + var value = try FilterValues.produced(.array((0..<100).map { .number(JSONNumber(integer: $0)) }), context: &context) + var vm = try FilterVM(program: program, input: value, context: &context) + try value.release(context: &context) + struct NoInput: FilterInput { mutating func next(context: inout CLIJobContext) throws -> FilterYield? { nil } } + var input = NoInput() + var pending = try vm.next(input: &input, context: &context) + var first = try #require(pending); pending = nil + try first.release(context: &context) + context.meter.checkpointObserver = { _ in withUnsafeCurrentTask { $0?.cancel() } } + #expect(throws: CancellationError.self) { _ = try vm.next(input: &input, context: &context) } + try vm.close(context: &context) + #expect(context.used(.runtimeFrames) == 0 && context.used(.collectionValues) == 0) + #expect(context.meter.used(.retainedBytes) == baseline) + } + try await task.value +} + +@Test func cliQueryConstructorsAndSelectorsReleaseScratchAfterMidOperationFailure() throws { + let object = try JSONParser().parse(Array("{\"a\":1,\"b\":2,\"c\":3,\"d\":4,\"e\":5,\"f\":6}".utf8)) + let array = JSON.array((0..<20).map { .number(JSONNumber(integer: $0)) }) + struct NoInput: FilterInput { mutating func next(context: inout CLIJobContext) throws -> FilterYield? { nil } } + for (source, value) in [("keys", object), (".[1:15]", array), ("map_values(.)", object), ("{a: .a,b: .b}", object), ("try error(.) catch .", object)] { + var compilation = try CLIJobContext() + let program = try FilterCompiler.compile(source, context: &compilation) + var rejected = 0 + for cap: UInt64 in [360, 720, 1440, 2880] { + var limits = CLILimits(); limits[.retainedBytes] = cap + var context = try CLIJobContext(limits: limits) + var vm = try FilterVM(program: program, input: .init(value: value), context: &context) + var input = NoInput() + do { while var result = try vm.next(input: &input, context: &context) { try result.release(context: &context) } } + catch { rejected += 1 } + try vm.close(context: &context) + #expect(context.used(.runtimeFrames) == 0 && context.used(.collectionValues) == 0) + #expect(context.meter.used(.retainedBytes) == 0, "\(source) cap=\(cap)") + } + #expect(rejected > 0) + } +} diff --git a/Tests/JSONCLITests/QueryCoreTests.swift b/Tests/JSONCLITests/QueryCoreTests.swift new file mode 100644 index 0000000..ef22c05 --- /dev/null +++ b/Tests/JSONCLITests/QueryCoreTests.swift @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +import Testing +import JSON +@testable import JSONFilterRuntime + +private struct EmptyFilterInput: FilterInput { + mutating func next(context: inout CLIJobContext) throws -> FilterYield? { nil } +} +private func evaluateFilter(_ source: String, input: String = "null") throws -> [JSON] { + var context = try CLIJobContext() + let program = try FilterCompiler.compile(source, context: &context) + let entry = context.meter.used(.retainedBytes) + let value = try JSONParser().parse(Array(input.utf8), meter: &context.meter) + let owned = context.meter.used(.retainedBytes) - entry + let lease = try CLIValueLease(ownedRetained: owned, context: &context) + var vm = try FilterVM(program: program, input: .init(value: value, lease: lease), context: &context) + defer { try? vm.close(context: &context) } + var input = EmptyFilterInput(), values: [JSON] = [] + while var result = try vm.next(input: &input, context: &context) { + values.append(result.value) + try result.release(context: &context) + } + return values +} +@Test func cliQueryCommaAndPipeAreOrderedPullStreams() throws { + #expect(try evaluateFilter("1,2 | .") == [.number(1), .number(2)]) + #expect(try evaluateFilter(".[] | .", input: "[1,2,3]") == [.number(1), .number(2), .number(3)]) + #expect(try evaluateFilter("[1,2,3]") == [.array([.number(1), .number(2), .number(3)])]) +} +@Test func cliQueryDecodedScalarKeysAndPresenceStayExact() throws { + let input = "{\"é\":1,\"é\":2,\"null\":null}" + #expect(try evaluateFilter(".[\"é\"], .[\"é\"]", input: input) == [.number(1), .number(2)]) + #expect(try evaluateFilter("has(\"null\"),has(\"missing\")", input: input) == [.boolean(true), .boolean(false)]) + #expect(try evaluateFilter(".missing?", input: input) == [.null]) +} +@Test func cliQueryWrongTypesAreCatchableButEmptyIsNotNull() throws { + #expect(try evaluateFilter(".[]?", input: "null").isEmpty) + #expect(try evaluateFilter("try .[] catch .", input: "null") == [.string("cannotIterate")]) + #expect(try evaluateFilter("empty // 9") == [.number(9)]) + #expect(try evaluateFilter("(false,null,0) // 9") == [.number(0)]) +} +@Test func cliQueryLogicalShortCircuitAndBranchesUseOriginalInput() throws { + #expect(try evaluateFilter("false and error(\"unreachable\")") == [.boolean(false)]) + #expect(try evaluateFilter("true or error(\"unreachable\")") == [.boolean(true)]) + #expect(try evaluateFilter("if (true,false) then . else null end", input: "7") == [.number(7), .null]) + #expect(try evaluateFilter("if . == 0 then 0 elif . == 1 then 2 else 3 end", input: "1") == [.number(2)]) +} +@Test func cliQueryMapsKeepAllResultsAndFirstUpdatePolicy() throws { + #expect(try evaluateFilter("map((.,.))", input: "[1,2]") == [.array([.number(1), .number(1), .number(2), .number(2)])]) + #expect(try evaluateFilter("map_values((.,9))", input: "[1,2]") == [.array([.number(1), .number(2)])]) + #expect(try evaluateFilter("map_values(empty)", input: "[1,2]") == [.array([])]) +} +@Test func cliQueryUnicodeLengthCountsScalarsAndRecursiveNullLeaves() throws { + #expect(try evaluateFilter("length,utf8bytelength", input: "\"é🦊\"") == [.number(3), .number(7)]) + #expect(try evaluateFilter("..", input: "[null,1]") == [.array([.null, .number(1)]), .null, .number(1)]) +} +@Test func cliQueryAliasesKeepSourceChargedUntilTheirLastRelease() throws { + var context = try CLIJobContext() + let baseline = context.meter.used(.retainedBytes) + var value = try FilterValues.produced(.array([.string(String(repeating: "x", count: 1000))]), context: &context) + var alias = value.selecting(value.value.arrayValue![0]) + try value.release(context: &context) + #expect(context.meter.used(.retainedBytes) > baseline) + try alias.release(context: &context) + #expect(context.meter.used(.retainedBytes) == baseline) +} +@Test func cliQueryTerminalQuotaStillReleasesValueStorage() throws { + var limits = CLILimits(); limits[.totalWorkUnits] = 2000 + var context = try CLIJobContext(limits: limits) + let baseline = context.meter.used(.retainedBytes) + var value = try FilterValues.produced(.string("owned"), context: &context) + do { try context.work(2000); Issue.record("Expected terminal work failure") } catch let failure as CLIError { #expect(failure.category == .resource) } + try value.release(context: &context) + #expect(context.meter.used(.retainedBytes) == baseline) + #expect(throws: CLIError.self) { try context.checkpoint() } +} +@Test func cliQueryObjectsUseCartesianOrderAndExactReplacement() throws { + let values = try evaluateFilter("{a:(1,2),b:(3,4)}") + let expected = try ["{\"a\":1,\"b\":3}","{\"a\":1,\"b\":4}","{\"a\":2,\"b\":3}","{\"a\":2,\"b\":4}"].map { try JSONParser().parse($0) } + #expect(values == expected) + let replacement = try evaluateFilter("{a:1,b:2,a:3}") + #expect(replacement[0].objectValue!.members.map(\.key.string) == ["a","b"]) + #expect(replacement[0].objectValue![JSONKey("a")] == .number(3)) + #expect(try evaluateFilter("{\"é\":1,\"é\":2}")[0].objectValue!.count == 2) +} +@Test func cliQuerySlicesUseScalarBoundariesAndCheckedNegativeIndices() throws { + #expect(try evaluateFilter(".[1:3]", input: "\"é🦊x\"") == [.string("́🦊")]) + #expect(try evaluateFilter(".[-2:]", input: "[1,2,3]") == [.array([.number(2),.number(3)])]) + #expect(try evaluateFilter(".[-1],.[1e1000]", input: "[1,2,3]") == [.number(3),.null]) +} +@Test func sharedFilterPlanHasIndependentConcurrentInvocations() async throws { + var compilation = try CLIJobContext() + let program = try FilterCompiler.compile(".a, .b", context: &compilation) + let outputs = try await withThrowingTaskGroup(of: [JSON].self, returning: [[JSON]].self) { group in + for _ in 0..<8 { + group.addTask { + var context = try CLIJobContext() + let input = try JSONParser().parse("{\"a\":1,\"b\":2}") + var value = try FilterValues.produced(input, context: &context) + var vm = try FilterVM(program: program, input: value, context: &context), source = EmptyFilterInput() + defer { try? vm.close(context: &context) } + try value.release(context: &context) + var results: [JSON] = [] + while var result = try vm.next(input: &source, context: &context) { results.append(result.value); try result.release(context: &context) } + #expect(context.used(.totalWorkUnits) > 0) + #expect(context.meter.used(.retainedBytes) == 0) + return results + } + } + var values: [[JSON]] = [] + for try await value in group { values.append(value) } + return values + } + #expect(outputs.count == 8 && outputs.allSatisfy { $0 == [.number(1), .number(2)] }) +} diff --git a/Tests/JSONCLITests/QueryInputTests.swift b/Tests/JSONCLITests/QueryInputTests.swift new file mode 100644 index 0000000..ab77db2 --- /dev/null +++ b/Tests/JSONCLITests/QueryInputTests.swift @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import Testing +import JSON +@testable import JSONCLIEngine +@testable import JSONFilterRuntime + +private func withInputFile(_ bytes: [UInt8], _ body: (String) throws -> T) throws -> T { + let root = FileManager.default.temporaryDirectory.appendingPathComponent("json-query-" + UUID().uuidString) + try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false) + defer { try? FileManager.default.removeItem(at: root) } + let path = root.appendingPathComponent("input.json") + try Data(bytes).write(to: path) + return try body(path.path) +} +@Test func cliQueryValueFramingRequiresWhitespaceAndIndependentFileEOF() throws { + try withInputFile(Array("1 2\n{}".utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path,path]) + defer { try? cursor.close(context: &context) } + var values: [JSON] = [] + while var value = try cursor.next(context: &context) { values.append(value.value); try value.release(context: &context) } + #expect(values.count == 6 && values[0] == .number(1) && values[3] == .number(1)) + } + try withInputFile(Array("{}[]".utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path]) + defer { try? cursor.close(context: &context) } + #expect(throws: JSONFailure.self) { _ = try cursor.next(context: &context) } + } +} +@Test func cliQueryDocumentWaitsForEOFAndTailErrorsAreTerminal() throws { + try withInputFile(Array("1 2".utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path], framing: .document) + defer { try? cursor.close(context: &context) } + #expect(throws: JSONFailure.self) { _ = try cursor.next(context: &context) } + #expect(throws: CLIError.self) { _ = try cursor.next(context: &context) } + } + try withInputFile(Array("1\n{\"bad\":]".utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path]) + defer { try? cursor.close(context: &context) } + let maybeFirst = try cursor.next(context: &context); var first = try #require(maybeFirst); #expect(first.value == .number(1)); try first.release(context: &context) + #expect(throws: JSONFailure.self) { try cursor.drain(context: &context) } + } +} +@Test func cliQueryStrictNDJSONAndRawLinesPreserveTheirDifferentContracts() throws { + try withInputFile(Array("1\n\n2\n".utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path], framing: .ndjson) + defer { try? cursor.close(context: &context) } + let maybeFirst = try cursor.next(context: &context); var first = try #require(maybeFirst); #expect(first.value == .number(1)); try first.release(context: &context) + #expect(throws: JSONFailure.self) { _ = try cursor.next(context: &context) } + } + try withInputFile(Array("a\r\n\n🦊".utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path], framing: .rawLines) + defer { try? cursor.close(context: &context) } + var values: [JSON] = [] + while var value = try cursor.next(context: &context) { values.append(value.value); try value.release(context: &context) } + #expect(values == [.string("a"), .string(""), .string("🦊")]) + } +} +@Test func cliQueryRootArrayYieldsProvisionallyAndRequiresClosingEOF() throws { + try withInputFile(Array("[1,2".utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path], framing: .array) + defer { try? cursor.close(context: &context) } + let maybeFirst = try cursor.next(context: &context); var first = try #require(maybeFirst); #expect(first.value == .number(1)); try first.release(context: &context) + let maybeSecond = try cursor.next(context: &context); var second = try #require(maybeSecond); #expect(second.value == .number(2)); try second.release(context: &context) + #expect(throws: JSONFailure.self) { _ = try cursor.next(context: &context) } + } +} +@Test(arguments: [2000, 20000]) func cliQueryManyTinyRecordsDoNotRetainTheWholeInputJob(records: Int) throws { + try withInputFile(Array(String(repeating: "0\n", count: records).utf8)) { path in + var context = try CLIJobContext(), cursor = CLIInputCursor(files: [path]) + defer { try? cursor.close(context: &context) } + var count = 0 + while var value = try cursor.next(context: &context) { count += 1; try value.release(context: &context) } + #expect(count == records) + #expect(context.meter.used(.retainedBytes) == 0) + // One 16 KiB syscall buffer may overlap one accepted 16 KiB chunk; + // the completed scalar/lease has a small fixed reservation. This bound + // follows the actual adapter, rather than an arbitrary RSS threshold. + let chunk = min(UInt64(16384), context.limits.core[.chunkBytes]) + #expect(context.meter.statistics().peakLogicalRetainedBytes <= 2 * chunk + 256) + #expect(context.used(.totalRecords) == UInt64(records) && context.used(.totalWorkUnits) > UInt64(records)) + } +} diff --git a/Tools/CLIQualification/Package.swift b/Tools/CLIQualification/Package.swift new file mode 100644 index 0000000..7d084fa --- /dev/null +++ b/Tools/CLIQualification/Package.swift @@ -0,0 +1,9 @@ +// swift-tools-version: 6.4 +// SPDX-License-Identifier: Apache-2.0 +import PackageDescription +let package = Package(name: "CLIQualification", platforms: [.macOS("27.0")], + products: [.executable(name: "CLIExactCompare", targets: ["CLIExactCompare"])], + dependencies: [.package(name: "Swift-JSON", path: "../..")], + targets: [.executableTarget(name: "CLIExactCompare", dependencies: [ + .product(name: "JSON", package: "Swift-JSON"), .product(name: "JSONStreaming", package: "Swift-JSON")])], + swiftLanguageModes: [.v6]) diff --git a/Tools/CLIQualification/Sources/CLIExactCompare/Main.swift b/Tools/CLIQualification/Sources/CLIExactCompare/Main.swift new file mode 100644 index 0000000..8b656bb --- /dev/null +++ b/Tools/CLIQualification/Sources/CLIExactCompare/Main.swift @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +import Foundation +import JSON +import JSONStreaming +#if canImport(Darwin) +import Darwin +#else +import Glibc +#endif + +private struct Bytes: AsyncSequence { + typealias Element = JSONByteChunk + let bytes: [UInt8] + struct Iterator: AsyncIteratorProtocol { + let bytes: [UInt8] + var index = 0 + mutating func next() async -> JSONByteChunk? { + guard index < bytes.count else { return nil } + let end = Swift.min(index + 16384, bytes.count) + defer { index = end } + return .init(Array(bytes[index.. Iterator { .init(bytes: bytes) } +} +@main struct CLIExactCompare { + static func main() async { + do { + let arguments = CommandLine.arguments + guard arguments.count == 3 else { throw NSError(domain: "qualification", code: 2) } + let actual = try bounded(arguments[1]), expected = try bounded(arguments[2]) + let declaration = try JSONParser().parse(expected) + guard case .array(let texts) = declaration else { throw NSError(domain: "qualification", code: 2) } + // CLI result streams may be empty. The library's value sequence + // requires a record; verify the zero-result contract explicitly. + if texts.isEmpty { + guard actual.allSatisfy({ [9,10,13,32].contains($0) }) else { throw NSError(domain: "qualification", code: 1) } + print("NATIVE_EXACT_COMPARE passed values=0"); return + } + var iterator = JSONValueSequence(Bytes(bytes: actual), options: .init(framing: .whitespaceSeparatedValues)).makeAsyncIterator() + var count = 0 + for text in texts { + guard case .string(let raw) = text, let actual = try await iterator.next() else { throw NSError(domain: "qualification", code: 1) } + let expected = try JSONParser().parse(raw) + guard try JSONComparison.equals(actual, expected) else { throw NSError(domain: "qualification", code: 1) } + count += 1 + } + guard try await iterator.next() == nil else { throw NSError(domain: "qualification", code: 1) } + print("NATIVE_EXACT_COMPARE passed values=\(count)") + } catch { fputs("NATIVE_EXACT_COMPARE failed\n", stderr); exit(1) } + } + private static func bounded(_ path: String) throws -> [UInt8] { + let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path)) + defer { try? handle.close() } + var bytes: [UInt8] = [] + while let data = try handle.read(upToCount: 16384), !data.isEmpty { + guard bytes.count + data.count <= 33554432 else { throw NSError(domain: "qualification", code: 2) } + bytes.append(contentsOf: data) + } + return bytes + } +} From 1e82158de754888eda1b22566c1e7260470eeaed Mon Sep 17 00:00:00 2001 From: Thorsten Fuchs <76904008+ThorFuchs@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:09:05 +0200 Subject: [PATCH 3/6] fix(qualification): avoid mutable Glibc stderr in exact consumer --- Documentation/Architecture/Release1.4/Qualification.md | 10 ++++++++++ .../Sources/CLIExactCompare/Main.swift | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Documentation/Architecture/Release1.4/Qualification.md b/Documentation/Architecture/Release1.4/Qualification.md index ededc33..b3d4622 100644 --- a/Documentation/Architecture/Release1.4/Qualification.md +++ b/Documentation/Architecture/Release1.4/Qualification.md @@ -79,3 +79,13 @@ source remains identical; final C5 qualification will use the integration head. The inherited 168 corpus rows and auxiliary binding modes still need their complete mode-specific dispositions. Arithmetic and advanced filters have separate gates. No simulator/device, Linux or WASI CLI success is inferred from this local run. + + +Hosted C2 Linux runs on x86_64/ARM64 passed 256 root functions/199 parameter cases, +ASan/TSan safety and the 53 CLI subprocess cases, then the comparator failed to +compile: Glibc's mutable `stderr` global is not concurrency-safe in async main. +The qualification tool now writes bounded failure text through +FileHandle.standardError. No unsafe annotation/flag or library behavior changed. +Failed run 38593758299 and its actual compiler log are retained; a fresh hosted +run is required before Linux closure. Local comparator rebuild and all 38 C2 +vectors verify the diagnostic-only fix on the supported Mac. diff --git a/Tools/CLIQualification/Sources/CLIExactCompare/Main.swift b/Tools/CLIQualification/Sources/CLIExactCompare/Main.swift index 8b656bb..0a9ae2f 100644 --- a/Tools/CLIQualification/Sources/CLIExactCompare/Main.swift +++ b/Tools/CLIQualification/Sources/CLIExactCompare/Main.swift @@ -47,7 +47,7 @@ private struct Bytes: AsyncSequence { } guard try await iterator.next() == nil else { throw NSError(domain: "qualification", code: 1) } print("NATIVE_EXACT_COMPARE passed values=\(count)") - } catch { fputs("NATIVE_EXACT_COMPARE failed\n", stderr); exit(1) } + } catch { FileHandle.standardError.write(Data("NATIVE_EXACT_COMPARE failed\n".utf8)); exit(1) } } private static func bounded(_ path: String) throws -> [UInt8] { let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path)) From bcb2a9735bb6de67c9096f44d7494d3d5b73fc8a Mon Sep 17 00:00:00 2001 From: Thorsten Fuchs <76904008+ThorFuchs@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:03:23 +0200 Subject: [PATCH 4/6] feat(cli): add bounded exact decimal arithmetic and pull range --- .../Architecture/ExtractionManifest.json | 5 +- .../Architecture/Release1.4/Acceptance.json | 76 +- .../Architecture/Release1.4/Arithmetic.md | 59 + .../Architecture/Release1.4/Progress.json | 34 +- .../Architecture/Release1.4/Qualification.md | 32 + README.md | 7 +- Scripts/ci.py | 20 +- Scripts/generate_arithmetic_oracle.py | 58 + Scripts/qualify_cli_cases.py | 12 +- Scripts/qualify_cli_limits.py | 42 + .../JSON/Documentation.docc/CommandLine.md | 25 +- Sources/JSONCLIEngine/CLIDiagnostics.swift | 24 +- Sources/JSONCLIEngine/CLIEngine.swift | 5 +- Sources/JSONCLIEngine/CLIQueryCommand.swift | 11 +- Sources/JSONFilterRuntime/CLIJobContext.swift | 19 +- .../JSONFilterRuntime/DecimalMagnitude.swift | 160 + Sources/JSONFilterRuntime/ExactDecimal.swift | 184 + .../JSONFilterRuntime/FilterCompiler.swift | 22 +- .../JSONFilterRuntime/FilterOperators.swift | 134 + Sources/JSONFilterRuntime/FilterVM.swift | 57 +- Sources/JSONFilterRuntime/FilterValues.swift | 6 + Sources/JSONFilterRuntime/RangeState.swift | 28 + Tests/Fixtures/CLI/ArithmeticOracle.json | 5858 +++++++++++++++++ Tests/JSONCLITests/ArithmeticTests.swift | 147 + 24 files changed, 6964 insertions(+), 61 deletions(-) create mode 100644 Documentation/Architecture/Release1.4/Arithmetic.md create mode 100644 Scripts/generate_arithmetic_oracle.py create mode 100644 Scripts/qualify_cli_limits.py create mode 100644 Sources/JSONFilterRuntime/DecimalMagnitude.swift create mode 100644 Sources/JSONFilterRuntime/ExactDecimal.swift create mode 100644 Sources/JSONFilterRuntime/FilterOperators.swift create mode 100644 Sources/JSONFilterRuntime/RangeState.swift create mode 100644 Tests/Fixtures/CLI/ArithmeticOracle.json create mode 100644 Tests/JSONCLITests/ArithmeticTests.swift diff --git a/Documentation/Architecture/ExtractionManifest.json b/Documentation/Architecture/ExtractionManifest.json index 4d9844a..8047a0b 100644 --- a/Documentation/Architecture/ExtractionManifest.json +++ b/Documentation/Architecture/ExtractionManifest.json @@ -209,5 +209,8 @@ "framework_boundary": "Conditional CoreTransferable/UniformTypeIdentifiers plus a narrow explicit main-actor Apple PDF boundary using WebKit, CoreText, CoreGraphics and AppKit/UIKit paragraph styles. Core JSON imports none of these; no new root dependency.", "rtf": "Original bounded strict-token emitter; AppKit/UIKit native decoding only in tests and qualification consumers, not generation.", "pdf": "Original invocation-owned lifecycle, finite adapter accounting and visible-range pagination using Apple SDK APIs. PDFKit is independent qualification only; no third-party renderer adapted." - } + }, + "original_notes": [ + "1.4 C3 decimal magnitude, rational/quantization kernel, pull range and type-directed overloads are original Apache-2.0 code. The 738-case deterministic oracle uses Python integer/Fraction arithmetic; no jq/Rhoe arithmetic implementation was copied." + ] } diff --git a/Documentation/Architecture/Release1.4/Acceptance.json b/Documentation/Architecture/Release1.4/Acceptance.json index d61ac68..e9dcb45 100644 --- a/Documentation/Architecture/Release1.4/Acceptance.json +++ b/Documentation/Architecture/Release1.4/Acceptance.json @@ -1,7 +1,7 @@ { "schema": "swift-json-cli-acceptance/1", "prepared": "2026-10-14", - "implementation_status": "Foundation and pull query-core milestones; complete C0-C5 integration remains unqualified", + "implementation_status": "Foundation, query core and exact arithmetic milestones; C4/C5 integration remains unqualified", "requirements": [ { "id": "CL001", @@ -666,7 +666,7 @@ "title": "Exact add/subtract/multiply", "verification": "Carry/borrow/sign/zero/large integer cases against independent exact expectations.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, "fixture_ids": [ "Cartesian-add-order", @@ -674,6 +674,10 @@ "exact-decimal-add", "exact-borrow", "exact-multiply" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" ] }, { @@ -682,9 +686,13 @@ "title": "Numeric admission and lexemes", "verification": "Computed values normalized with no false originalLexeme; untouched inputs preserve spelling.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, - "fixture_ids": [] + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" + ] }, { "id": "CL047", @@ -692,10 +700,14 @@ "title": "Terminating exact division", "verification": "Reduced denominators containing only2/5, large common factors and mixed signs.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, "fixture_ids": [ "terminating-division" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" ] }, { @@ -704,10 +716,14 @@ "title": "Repeating division default", "verification": "1/3 and repeating values give language inexactDivision; no floating fallback.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, "fixture_ids": [ "repeating-division-error" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" ] }, { @@ -716,12 +732,16 @@ "title": "Explicit scale/half-even", "verification": "Required option pair, decimal places, finite caps, signed ties and report rounding counts.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, "fixture_ids": [ "rounded-third", "half-even-ties", "scale-pair-required" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" ] }, { @@ -730,11 +750,15 @@ "title": "Zero division and integral remainder", "verification": "Typed catchable domain errors; signed truncated remainder; fractional remainder rejected.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, "fixture_ids": [ "negative-remainder", "fractional-remainder-rejected" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" ] }, { @@ -743,12 +767,16 @@ "title": "Exact numeric builtins", "verification": "abs/floor/ceil/trunc/round/range/add with negative and empty/domain cases; round ties away from zero.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, "fixture_ids": [ "round-away-from-zero", "range-negative-step", "range-zero-step" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" ] }, { @@ -757,10 +785,14 @@ "title": "Exponent/growth preflight", "verification": "Large sparse exponent operations fail finite caps before dense allocation; exact zero shortcut allowed.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, "fixture_ids": [ "exponent-gap-cap" + ], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" ] }, { @@ -769,9 +801,13 @@ "title": "Numeric work and cancellation", "verification": "Charged GCD/division/carry inner loops; boundaries and checkpoint cancellation; terminal errors not catchable.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, - "fixture_ids": [] + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" + ] }, { "id": "CL054", @@ -779,9 +815,13 @@ "title": "32-bit arithmetic safety", "verification": "No Int/UInt64 negation/narrowing/product overflow traps; wasm32 kernel tests or explicit gate blocker.", "obligation": "mandatory", - "status": "not_run", + "status": "partial", "evidence_required": true, - "fixture_ids": [] + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" + ] }, { "id": "CL055", @@ -789,9 +829,13 @@ "title": "No Decimal/Double authority", "verification": "Source audit plus precision cases prove no foreign arithmetic/fallback authority.", "obligation": "mandatory", - "status": "not_run", + "status": "passed", "evidence_required": true, - "fixture_ids": [] + "fixture_ids": [], + "evidence": [ + "Documentation/Architecture/Release1.4/Arithmetic.md", + "Documentation/Architecture/Release1.4/Qualification.md" + ] }, { "id": "CL056", diff --git a/Documentation/Architecture/Release1.4/Arithmetic.md b/Documentation/Architecture/Release1.4/Arithmetic.md new file mode 100644 index 0000000..2ec8f69 --- /dev/null +++ b/Documentation/Architecture/Release1.4/Arithmetic.md @@ -0,0 +1,59 @@ +# Exact filter arithmetic + +C3 builds on query core 6e8f145 / draft PR #24. Original private code implements +base-ten unsigned working magnitudes plus native sign/coefficient/exponent values. +No Double, Float or Foundation Decimal is an arithmetic authority. Numeric binary +operators clear originalLexeme and normalize computed zero. Untouched selected +values preserve their admitted spelling; null's identity overload selects a value. + +Addition bounds exponent alignment before dense storage. Schoolbook multiplication +uses UInt16: digit product + stored digit + carry <=99; addition <=19. Subtraction +fits Int16. Long division maintains r