toolbound 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. toolbound-0.2.0/.gitignore +19 -0
  2. toolbound-0.2.0/CHANGELOG.md +142 -0
  3. toolbound-0.2.0/LICENSE +202 -0
  4. toolbound-0.2.0/PKG-INFO +547 -0
  5. toolbound-0.2.0/README.md +509 -0
  6. toolbound-0.2.0/pyproject.toml +105 -0
  7. toolbound-0.2.0/src/toolbound/__init__.py +123 -0
  8. toolbound-0.2.0/src/toolbound/adapters/__init__.py +9 -0
  9. toolbound-0.2.0/src/toolbound/adapters/_common.py +84 -0
  10. toolbound-0.2.0/src/toolbound/adapters/anthropic.py +81 -0
  11. toolbound-0.2.0/src/toolbound/adapters/openai.py +115 -0
  12. toolbound-0.2.0/src/toolbound/cli.py +69 -0
  13. toolbound-0.2.0/src/toolbound/core/__init__.py +4 -0
  14. toolbound-0.2.0/src/toolbound/core/approval.py +314 -0
  15. toolbound-0.2.0/src/toolbound/core/budget.py +42 -0
  16. toolbound-0.2.0/src/toolbound/core/context.py +56 -0
  17. toolbound-0.2.0/src/toolbound/core/contracts.py +689 -0
  18. toolbound-0.2.0/src/toolbound/core/credentials.py +41 -0
  19. toolbound-0.2.0/src/toolbound/core/errors.py +117 -0
  20. toolbound-0.2.0/src/toolbound/core/exposure.py +68 -0
  21. toolbound-0.2.0/src/toolbound/core/gateway.py +1461 -0
  22. toolbound-0.2.0/src/toolbound/core/network.py +267 -0
  23. toolbound-0.2.0/src/toolbound/core/observe.py +60 -0
  24. toolbound-0.2.0/src/toolbound/core/paths.py +138 -0
  25. toolbound-0.2.0/src/toolbound/core/ports.py +166 -0
  26. toolbound-0.2.0/src/toolbound/core/pyschema.py +119 -0
  27. toolbound-0.2.0/src/toolbound/core/ratelimit.py +65 -0
  28. toolbound-0.2.0/src/toolbound/core/result.py +100 -0
  29. toolbound-0.2.0/src/toolbound/core/schema.py +201 -0
  30. toolbound-0.2.0/src/toolbound/core/shaping.py +711 -0
  31. toolbound-0.2.0/src/toolbound/core/signing.py +137 -0
  32. toolbound-0.2.0/src/toolbound/core/state.py +102 -0
  33. toolbound-0.2.0/src/toolbound/estimators.py +24 -0
  34. toolbound-0.2.0/src/toolbound/loaders/__init__.py +202 -0
  35. toolbound-0.2.0/src/toolbound/loaders/meta_schema.py +162 -0
  36. toolbound-0.2.0/src/toolbound/py.typed +0 -0
  37. toolbound-0.2.0/src/toolbound/sync.py +76 -0
  38. toolbound-0.2.0/src/toolbound/transport/__init__.py +19 -0
  39. toolbound-0.2.0/src/toolbound/transport/http.py +269 -0
  40. toolbound-0.2.0/src/toolbound/transport/mock.py +89 -0
  41. toolbound-0.2.0/tests/__init__.py +0 -0
  42. toolbound-0.2.0/tests/conftest.py +57 -0
  43. toolbound-0.2.0/tests/integrations/__init__.py +0 -0
  44. toolbound-0.2.0/tests/integrations/conftest.py +32 -0
  45. toolbound-0.2.0/tests/integrations/test_fastapi.py +149 -0
  46. toolbound-0.2.0/tests/integrations/test_langgraph.py +128 -0
  47. toolbound-0.2.0/tests/security/__init__.py +0 -0
  48. toolbound-0.2.0/tests/security/conftest.py +112 -0
  49. toolbound-0.2.0/tests/security/test_approval.py +220 -0
  50. toolbound-0.2.0/tests/security/test_approval_tokens.py +191 -0
  51. toolbound-0.2.0/tests/security/test_authz.py +204 -0
  52. toolbound-0.2.0/tests/security/test_budget_dos.py +77 -0
  53. toolbound-0.2.0/tests/security/test_credentials.py +175 -0
  54. toolbound-0.2.0/tests/security/test_cursors.py +107 -0
  55. toolbound-0.2.0/tests/security/test_effects.py +117 -0
  56. toolbound-0.2.0/tests/security/test_hardening.py +271 -0
  57. toolbound-0.2.0/tests/security/test_isolation.py +208 -0
  58. toolbound-0.2.0/tests/security/test_replay_store.py +106 -0
  59. toolbound-0.2.0/tests/security/test_review2.py +505 -0
  60. toolbound-0.2.0/tests/security/test_ssrf.py +317 -0
  61. toolbound-0.2.0/tests/security/test_transport_ssrf.py +258 -0
  62. toolbound-0.2.0/tests/security/test_unsafe_retries.py +211 -0
  63. toolbound-0.2.0/tests/test_adapters_and_loader.py +225 -0
  64. toolbound-0.2.0/tests/test_architecture.py +70 -0
  65. toolbound-0.2.0/tests/test_examples.py +102 -0
  66. toolbound-0.2.0/tests/test_python_tools.py +224 -0
  67. toolbound-0.2.0/tests/test_rest.py +493 -0
  68. toolbound-0.2.0/tests/test_shaping.py +270 -0
  69. toolbound-0.2.0/tests/test_smoke_rest.py +50 -0
@@ -0,0 +1,19 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .hypothesis/
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ .coverage
12
+ htmlcov/
13
+ .env
14
+ .env.*
15
+ !.env.example
16
+ *.pem
17
+ *.key
18
+ .DS_Store
19
+ benchmarks/results/*.local.json
@@ -0,0 +1,142 @@
1
+ # Changelog
2
+
3
+ All notable changes are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [Semantic Versioning](https://semver.org/) (before 1.0, minor versions may break the API).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0] - unreleased
10
+
11
+ Security-hardening release after an independent production-readiness review of 0.1.0.
12
+ **Everyone on 0.1.0 should upgrade.** 0.1.0 was never published to PyPI.
13
+
14
+ ### Security
15
+
16
+ - **Critical:** `SecureTransport` stored upstream cookies in a client shared by all users,
17
+ so a `Set-Cookie` from one user's call was sent on later calls for other users and
18
+ tenants. The transport no longer stores cookies.
19
+ - **High:** `Operation(method="DELETE")` without `effect` was treated as a read tool (no
20
+ approval, no duplicate-call protection). The effect is now inferred from the method.
21
+ - **High:** `Gateway.grant()` signed any `PendingApproval` object, so a host that rebuilt
22
+ one from client data could execute arguments the human never saw. Approval requests are
23
+ now gateway-issued encrypted tokens, and only the token is trusted.
24
+ - **High:** trimming a large result to the token budget could block the event loop for
25
+ seconds. Trimming work is bounded, and large bodies are processed in a worker thread.
26
+ - **Medium:** cursors were signed but readable (they exposed upstream tokens and trusted
27
+ path values). They are now encrypted.
28
+ - **Medium:** a timed-out or interrupted write told the model `retryable: true`, inviting a
29
+ duplicate write. Such failures are now `outcome_unknown` (not retryable), and 5xx after
30
+ a write is not retryable.
31
+ - **Medium:** `MemoryStateStore` evicted unexpired replay keys when full, so an approval
32
+ could be reused. It now refuses new claims (fail closed). Any `StateStore` error fails
33
+ the call closed.
34
+ - **Medium:** approval summaries were truncated silently and hid trusted values and the
35
+ target URL. They now show the method, resolved URL, all arguments and trusted values,
36
+ escape control and bidi characters, and flag truncation.
37
+ - **Medium:** no warning for an ephemeral signing key. A `ToolboundWarning` is now emitted.
38
+ - **Low:** `HttpRequest`/`HttpResponse` reprs no longer print headers, bodies or query
39
+ values. The rate limiter forgets idle callers and is thread-safe. A per-call
40
+ `Limits.total_timeout` bounds retries and `Retry-After` waits. The transport falls back
41
+ to the next checked address on connect failure. Key-based redaction applies to renamed
42
+ fields. Non-object list items are reported and continuation offsets stay aligned. An
43
+ explicit special-purpose IP blocklist no longer depends on the Python patch version.
44
+ Unenforced JSON Schema `format`s produce a warning.
45
+
46
+ ### Security (second, adversarial review of 0.2.0)
47
+
48
+ - **High:** a secret-detection regex was quadratic: an 800 KB response froze the process
49
+ for 71 s (threads did not help, the regex holds the GIL). Patterns are now linear and
50
+ only scan what can still be returned.
51
+ - **High:** when the last retry of a write failed before sending, an earlier attempt that
52
+ had reached the upstream was hidden and the model saw a retryable error. Any attempt or
53
+ redirect hop that may have landed now makes a write `outcome_unknown`; so does a 3xx.
54
+ - **High:** echoed credentials in URL-encoded, JSON-escaped or base64 form, or used as
55
+ object keys, were not scrubbed, and `accessToken`-style keys escaped key redaction.
56
+ - **Medium-High:** automatic retries followed the HTTP method, so a destructive GET was
57
+ retried. Only declared reads (or idempotency-keyed calls) are retried now.
58
+ - **Medium:** cursors were not bound to trusted bound values; nested schemas with
59
+ `properties` but a non-`"object"` type stayed open (mass assignment); one actor could
60
+ exhaust the replay store for everyone; the FastAPI example let the agent approve its own
61
+ request and did not make rejections final.
62
+ - **Low:** the per-actor rate limit was not tenant-qualified; hidden group actions could
63
+ be told apart from missing ones; a duplicate call id or a pre-send failure used up an
64
+ approval; IPv4-translated (`::ffff:0:a.b.c.d`) addresses were allowed; concurrency limits
65
+ were keyed by service name only; stuck sync tools could starve DNS resolution; missing
66
+ provider call ids became the string `"None"`; release tooling was unpinned and releases
67
+ were not restricted to commits on `main`.
68
+
69
+ ### Breaking
70
+
71
+ - `PythonTool` and `@gateway.tool` require `effect=`.
72
+ - `POST`/`PUT`/`PATCH` operations default to `effect="write"` and `DELETE` to
73
+ `"destructive"`. Declare `effect="read"` for read-only POSTs. `DELETE` + `read` is
74
+ rejected.
75
+ - `Gateway.grant()`/`resume()` accept a gateway-issued `PendingApproval` or its `token`.
76
+ Hand-built `PendingApproval` objects are rejected with `ApprovalInvalid`. Grant tokens
77
+ changed prefix (`tba1` → `tba2`).
78
+ - Cursor format changed (`tbc1` → `tbc2`); 0.1 cursors are rejected.
79
+ - New runtime dependency: `cryptography>=42`.
80
+ - New error code `outcome_unknown`. Writes that may have reached the upstream are no longer
81
+ marked retryable.
82
+
83
+ ### Added
84
+
85
+ - `Gateway.pending_approval(token)`; `PendingApproval.token`, `.target`,
86
+ `.summary_truncated`; `to_dict()` now includes the token and trusted values.
87
+ - `ApprovalInvalid`, `StateStoreError`, `ToolboundWarning`; `Limits.total_timeout`.
88
+ - `Gateway.deny()` / `SyncGateway.deny()`; `SyncGateway.resume(call_id=...)`;
89
+ `MemoryStateStore(max_per_partition=...)`.
90
+ - Gateways can be shared across event loops and threads.
91
+ - FastAPI and LangGraph examples with automated tests (`tests/integrations`).
92
+ - Dedicated security regression suite (`tests/security`, `pytest -m security`), threat
93
+ model (`docs/security/threat-model.md`), vulnerability process
94
+ (`docs/security/vulnerability-process.md`).
95
+ - CI: separate security-suite job, `pip-audit`, dependency review, CodeQL, `zizmor`,
96
+ actions pinned by commit SHA, and a single `required checks` gate. The release workflow
97
+ refuses to build unless all of them pass.
98
+ - README: why Toolbound exists, a worked example with a comparison diagram, and a "how a
99
+ tool call flows" diagram.
100
+
101
+ ### Fixed
102
+
103
+ - `Gateway(state=...)`, `estimator=` and `observer=` were replaced by defaults when the
104
+ given object was falsy (for example an empty store with `__len__`).
105
+ - Docs: removed the claim that the model cannot read cursor contents (true only from 0.2),
106
+ corrected the LangChain recipe (it lost tool-call ids), and corrected the event-loop
107
+ guidance.
108
+
109
+ ## [0.1.0] - 2026-11-10
110
+
111
+ First public alpha.
112
+
113
+ ### Added
114
+
115
+ - `Gateway`: one execution pipeline for REST operations and trusted Python tools
116
+ (validation, trusted bindings, scopes, required `authorize` hook, rate limits, approval,
117
+ replay protection, execution, output shaping, structured results).
118
+ - Contracts in Python (`Service`, `Operation`, `PythonTool`, `Output`, `Field`,
119
+ `Pagination`, policies) and in YAML/JSON (format version 1, published meta-schema,
120
+ `status: draft` review gate).
121
+ - `SecureTransport`: DNS resolution with every address checked, IP pinning with TLS host
122
+ verification, non-public ranges blocked unless explicitly trusted, no proxies from the
123
+ environment, decompressed size caps, total deadlines.
124
+ - Redirect policy (off by default; per-hop checks; credentials dropped across origins).
125
+ - Output pipeline: allowlist projection with a safe path language, nested shapes,
126
+ transforms, defaults, limits, key/secret/credential redaction, PII hooks, output schema
127
+ checks, token budgets with `truncate`/`reject`, required evidence fields, transparent
128
+ omission metadata.
129
+ - Signed, expiring, argument-bound cursors for upstream cursor/link/page pagination and
130
+ local continuation.
131
+ - Approval grants bound to actor, tenant, tool, contract fingerprint and arguments;
132
+ single use; inline approver or two-step `grant`/`resume`.
133
+ - Duplicate call-id protection for writes; idempotency keys; retries only when safe.
134
+ - Exposure strategies `direct` and compact `grouped`; scope, visibility and tag filtering.
135
+ - Adapters for the Anthropic Messages API and OpenAI Chat Completions / Responses.
136
+ - `SyncGateway`, `TiktokenEstimator`, `LoggingObserver`, `MockTransport`, CLI
137
+ (`validate`, `tools`, `schema`).
138
+ - Security regression suite, property-based tests, runnable examples, reproducible
139
+ benchmarks, documentation.
140
+
141
+ [Unreleased]: https://github.com/SaeedNoshadi89/toolbound/compare/v0.1.0...HEAD
142
+ [0.1.0]: https://github.com/SaeedNoshadi89/toolbound/releases/tag/v0.1.0
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+