Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Changelog

## 1.1.0 — unreleased candidate
## Unreleased

- Keep object-schema construction and document admission within one cumulative
budget, including live retention and terminal owned failures/cancellation.
Tight limits that previously fit each phase separately can now fail the composed
call. Public signatures and finite default profiles are unchanged.
- Update release documentation for the published 1.1.0 package and its macOS,
iOS and iPadOS 27 baseline.

## 1.1.0 — 2026-10-12

- Scoped typed reads with cumulative reports, native model projection and guarded
cursor invalidation; explicit numeric fidelity and absent/null presence.
Expand All @@ -10,7 +19,7 @@
- macOS/iOS 27 deployment minima and corrected schema bundle layout; public
SDK/simulator and disk-backed SwiftData consumers. Full physical-device
qualification follows the approved 1.1 matrix: physical iPhone required, iPad
SDK/simulator acceptance with physical iPad deferred. No tag/publication follows.
SDK/simulator acceptance with physical iPad deferred.
- Correct native ISO-8601 calendar fields and strict Base64 unused padding bits.
See API migration guidance; no limits/profile weights or dependencies raised.
- Release failed Base64 scratch from logical retention while preserving consumed
Expand Down
9 changes: 9 additions & 0 deletions Documentation/Architecture/SchemaAndRegex.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ These contracts describe the public behavior and its resource boundaries.

JSONSchemaDocument accepts only JSON.boolean or JSON.object as the schema root. It preserves the complete raw document, optional retrieval URI and source map. A builder produces ordinary JSONSchemaDocument input. It does not bypass validation or discard unknown annotation keywords.

`JSONSchemaBuilder.object(limits:_:)` uses one schema-domain meter from the
accumulator's outer frame through document admission. Work, numeric work, aggregate
admission counters and live ownership remain charged across both phases. Admission
scratch overlaps the constructed object's live logical retention. A resource failure
or cancellation caught inside the callback remains terminal; finalization cannot
return a document from that failed invocation. Replacing the borrowed accumulator
with a different invocation fails with invalidOperation. Caller callback work is outside
owned quotas. Compilation is a separate public operation.

JSONSchemaRegistry is an immutable bounded snapshot of explicitly supplied resource documents. Updating it produces a new snapshot. There is no automatic URLSession resolver or mutable global registry. A caller fetches resources through its own authorized application I/O before constructing the snapshot. Each compile consumes the selected snapshot within the compiler's own cumulative limits.

Use JSONSchemaCompiler(options:).compile(document, registry:) throws -> CompiledJSONSchema and JSONSchemaValidator(options:).validate(instance, against:) throws -> JSONValidationResult. Compiled plans are checked Sendable and immutable; validators store configuration only. Plan IDs, resource IDs, keyword IDs and reference targets use bounded private indices. Avoid exposing upstream Schema, Context, OrderedDictionary or Regex types in signatures.
Expand Down
90 changes: 45 additions & 45 deletions Documentation/Qualification/LimitsMatrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 113,
"line": 167,
"code": "for metric: JSONResourceMetric in [.nodes, .aggregateTextBytes, .outputBytes, .inputBytes] { try retire(metric) }"
},
{
Expand Down Expand Up @@ -92,7 +92,7 @@
},
{
"path": "Sources/JSON/Construction/JSONObjectAccumulator.swift",
"line": 7,
"line": 21,
"code": "try initialMeter.require(.maxDepth, 1); try initialMeter.charge(.nodes); try initialMeter.retain(JSONLogicalWeights.node)"
},
{
Expand Down Expand Up @@ -187,7 +187,7 @@
},
{
"path": "Sources/JSON/Construction/JSONObjectAccumulator.swift",
"line": 7,
"line": 21,
"code": "try initialMeter.require(.maxDepth, 1); try initialMeter.charge(.nodes); try initialMeter.retain(JSONLogicalWeights.node)"
},
{
Expand All @@ -202,12 +202,12 @@
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 46,
"line": 87,
"code": "if metric == .nodes && domain == .schemaCompilation {"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 113,
"line": 167,
"code": "for metric: JSONResourceMetric in [.nodes, .aggregateTextBytes, .outputBytes, .inputBytes] { try retire(metric) }"
},
{
Expand Down Expand Up @@ -377,7 +377,7 @@
},
{
"path": "Sources/JSON/Construction/JSONObjectAccumulator.swift",
"line": 11,
"line": 33,
"code": "try meter.require(.objectMembers, UInt64(members.count) + 1)"
},
{
Expand Down Expand Up @@ -542,7 +542,7 @@
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 113,
"line": 167,
"code": "for metric: JSONResourceMetric in [.nodes, .aggregateTextBytes, .outputBytes, .inputBytes] { try retire(metric) }"
},
{
Expand Down Expand Up @@ -702,42 +702,42 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 53,
"line": 94,
"code": "if metric == .retainedBytes { peak = max(peak, next) }"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 80,
"line": 121,
"code": "let retained = try adding(.retainedBytes,used(.retainedBytes),bytes)"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 84,
"line": 125,
"code": "counters[JSONResourceMetric.retainedBytes.index] = retained; peak = max(peak,retained)"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 88,
"line": 142,
"code": "guard bytes <= used(.retainedBytes) else { throw JSONFailure(.configuration, code: .invalidLimits) }"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 93,
"line": 147,
"code": "counters[JSONResourceMetric.retainedBytes.index] -= bytes"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 98,
"line": 152,
"code": "try checkpoint(); try require(.retainedBytes, liveBytes)"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 101,
"line": 155,
"code": "counters[JSONResourceMetric.retainedBytes.index] = liveBytes"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 105,
"line": 159,
"code": "if metric != .retainedBytes && metric != .regexRetainedBytes {"
},
{
Expand Down Expand Up @@ -852,17 +852,17 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 24,
"line": 45,
"code": "checkpointObserver?(used(.workUnits))"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 58,
"line": 99,
"code": "let total = try adding(.workUnits, used(.workUnits), amount)"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 73,
"line": 114,
"code": "counters[JSONResourceMetric.workUnits.index] = total"
}
],
Expand Down Expand Up @@ -1047,7 +1047,7 @@
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 113,
"line": 167,
"code": "for metric: JSONResourceMetric in [.nodes, .aggregateTextBytes, .outputBytes, .inputBytes] { try retire(metric) }"
},
{
Expand Down Expand Up @@ -1242,7 +1242,7 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 70,
"line": 111,
"code": "if amount >= limits[.cancellationQuantum] || sinceCancellationCheck >= limits[.cancellationQuantum] - amount {"
},
{
Expand Down Expand Up @@ -1337,22 +1337,22 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 47,
"line": 88,
"code": "let schemaNext = try adding(.schemaNodes, used(.schemaNodes), amount)"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 48,
"line": 89,
"code": "let (_, overflow) = retired[JSONResourceMetric.schemaNodes.index].addingReportingOverflow(schemaNext)"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 49,
"line": 90,
"code": "guard !overflow else { throw violation(.schemaNodes, nil, overflow: true) }"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 50,
"line": 91,
"code": "counters[JSONResourceMetric.schemaNodes.index] = schemaNext"
}
],
Expand Down Expand Up @@ -1392,7 +1392,7 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 61,
"line": 102,
"code": "case .schemaCompilation: domainMetric = .compileWorkUnits"
}
],
Expand All @@ -1412,22 +1412,22 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 81,
"line": 122,
"code": "let plan = try domain == .schemaCompilation ? adding(.compiledPlanBytes,used(.compiledPlanBytes),bytes) : nil"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 83,
"line": 124,
"code": "if let plan { counters[JSONResourceMetric.compiledPlanBytes.index] = plan }"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 90,
"line": 144,
"code": "guard bytes <= used(.compiledPlanBytes) else { throw JSONFailure(.configuration,code: .invalidLimits) }"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 91,
"line": 145,
"code": "counters[JSONResourceMetric.compiledPlanBytes.index] -= bytes"
}
],
Expand All @@ -1447,17 +1447,17 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 62,
"line": 103,
"code": "case .schemaEvaluation: domainMetric = .evaluationWorkUnits"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 69,
"line": 110,
"code": "let stagedEvaluation = try compilingEvaluation && domain == .schemaCompilation ? adding(.evaluationWorkUnits, used(.evaluationWorkUnits), amount) : nil"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 76,
"line": 117,
"code": "if let next = stagedEvaluation { counters[JSONResourceMetric.evaluationWorkUnits.index] = next }"
}
],
Expand Down Expand Up @@ -1542,12 +1542,12 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 121,
"line": 175,
"code": "guard bytes <= used(.annotationBytes) else { throw JSONFailure(.configuration, code: .invalidLimits) }"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 122,
"line": 176,
"code": "counters[JSONResourceMetric.annotationBytes.index] -= bytes"
},
{
Expand Down Expand Up @@ -1742,7 +1742,7 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 115,
"line": 169,
"code": "package mutating func beginRegexSearch() throws { try retire(.regexSearchWorkUnits) }"
},
{
Expand Down Expand Up @@ -1787,17 +1787,17 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 105,
"line": 159,
"code": "if metric != .retainedBytes && metric != .regexRetainedBytes {"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 117,
"line": 171,
"code": "guard bytes <= used(.regexRetainedBytes) else { throw JSONFailure(.configuration, code: .invalidLimits) }"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 118,
"line": 172,
"code": "counters[JSONResourceMetric.regexRetainedBytes.index] -= bytes"
},
{
Expand Down Expand Up @@ -1917,7 +1917,7 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 99,
"line": 153,
"code": "let lifetime: [JSONResourceMetric] = [.totalInputBytes, .totalRecords, .totalWorkUnits, .totalScannedNodes]"
},
{
Expand Down Expand Up @@ -1947,7 +1947,7 @@
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 99,
"line": 153,
"code": "let lifetime: [JSONResourceMetric] = [.totalInputBytes, .totalRecords, .totalWorkUnits, .totalScannedNodes]"
},
{
Expand Down Expand Up @@ -2007,12 +2007,12 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 64,
"line": 105,
"code": "case .streaming: domainMetric = .totalWorkUnits"
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 99,
"line": 153,
"code": "let lifetime: [JSONResourceMetric] = [.totalInputBytes, .totalRecords, .totalWorkUnits, .totalScannedNodes]"
}
],
Expand Down Expand Up @@ -2167,7 +2167,7 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 63,
"line": 104,
"code": "case .patch: domainMetric = .patchWorkUnits"
}
],
Expand All @@ -2192,7 +2192,7 @@
},
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 99,
"line": 153,
"code": "let lifetime: [JSONResourceMetric] = [.totalInputBytes, .totalRecords, .totalWorkUnits, .totalScannedNodes]"
}
],
Expand All @@ -2212,7 +2212,7 @@
"accounting_sites": [
{
"path": "Sources/JSON/Limits/JSONWorkMeter.swift",
"line": 97,
"line": 151,
"code": "resetRecord retires per-record state; selected scalar E09 retains no reusable scratch pool (zero bytes)."
}
],
Expand Down
9 changes: 9 additions & 0 deletions Documentation/Qualification/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ and does not replace independent qualification or any of the 49 cap probes.
Thread Sanitizer checks five shared-state cases, including concurrent payload
opening and separate read scopes over one immutable root. See [QA](../Architecture/Release1.1/QA.md).

The subsequent schema-builder accounting fix adds nine public behavior functions,
for exactly 184: JSON 97, JSONStreaming 14, JSONSchema 50, JSONRegexRuntime 12 and
JSONPatch 11. The 173 parameterized invocations and earlier qualification history
remain unchanged. The new cases cover cumulative core/schema work and admission,
N/N+1 live retention, terminal caught resource failures/cancellation, caller errors,
exact keys/numbers/order, separate compilation and callback replacement of the
invocation ledger. The four-product external
consumer also checks the 150-unit composed-budget regression.

| Independent input | Observed policy |
| --- | --- |
| JSONTestSuite, pinned in FixtureRegistry.json | 318 discovered: 101 accepted, 186 rejected, 31 strict/finite-policy rejections |
Expand Down
Loading