aweb-naapp 0.1.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 (203) hide show
  1. aweb_naapp-0.1.0/.gitignore +16 -0
  2. aweb_naapp-0.1.0/PKG-INFO +279 -0
  3. aweb_naapp-0.1.0/README.md +228 -0
  4. aweb_naapp-0.1.0/pyproject.toml +135 -0
  5. aweb_naapp-0.1.0/renderer/.gitignore +2 -0
  6. aweb_naapp-0.1.0/renderer/actions.mjs +704 -0
  7. aweb_naapp-0.1.0/renderer/build.mjs +30 -0
  8. aweb_naapp-0.1.0/renderer/catalogs.mjs +25 -0
  9. aweb_naapp-0.1.0/renderer/collections.mjs +481 -0
  10. aweb_naapp-0.1.0/renderer/dom.mjs +67 -0
  11. aweb_naapp-0.1.0/renderer/index.mjs +424 -0
  12. aweb_naapp-0.1.0/renderer/input.mjs +158 -0
  13. aweb_naapp-0.1.0/renderer/link-host.mjs +81 -0
  14. aweb_naapp-0.1.0/renderer/media.mjs +125 -0
  15. aweb_naapp-0.1.0/renderer/package-lock.json +704 -0
  16. aweb_naapp-0.1.0/renderer/package.json +20 -0
  17. aweb_naapp-0.1.0/renderer/playwright.config.mjs +13 -0
  18. aweb_naapp-0.1.0/renderer/sanitize.mjs +97 -0
  19. aweb_naapp-0.1.0/renderer/styles.css +665 -0
  20. aweb_naapp-0.1.0/renderer/tests/action-continuations.spec.mjs +188 -0
  21. aweb_naapp-0.1.0/renderer/tests/actions.spec.mjs +334 -0
  22. aweb_naapp-0.1.0/renderer/tests/collections.spec.mjs +148 -0
  23. aweb_naapp-0.1.0/renderer/tests/input.test.mjs +38 -0
  24. aweb_naapp-0.1.0/renderer/tests/links.test.mjs +31 -0
  25. aweb_naapp-0.1.0/renderer/tests/numbers.test.mjs +40 -0
  26. aweb_naapp-0.1.0/renderer/tests/render.spec.mjs +139 -0
  27. aweb_naapp-0.1.0/renderer/tests/schema.test.mjs +29 -0
  28. aweb_naapp-0.1.0/renderer/tests/update.spec.mjs +225 -0
  29. aweb_naapp-0.1.0/src/aweb_naapp/__init__.py +56 -0
  30. aweb_naapp-0.1.0/src/aweb_naapp/assets/aweb.css +849 -0
  31. aweb_naapp-0.1.0/src/aweb_naapp/assets/naapp-components.css +86 -0
  32. aweb_naapp-0.1.0/src/aweb_naapp/chrome.py +313 -0
  33. aweb_naapp-0.1.0/src/aweb_naapp/css.py +61 -0
  34. aweb_naapp-0.1.0/src/aweb_naapp/dispatch/__init__.py +401 -0
  35. aweb_naapp-0.1.0/src/aweb_naapp/dispatch/migrations/001_dispatches.sql +31 -0
  36. aweb_naapp-0.1.0/src/aweb_naapp/egress/__init__.py +61 -0
  37. aweb_naapp-0.1.0/src/aweb_naapp/egress/addresses.py +182 -0
  38. aweb_naapp-0.1.0/src/aweb_naapp/egress/headers.py +154 -0
  39. aweb_naapp-0.1.0/src/aweb_naapp/egress/refused.py +19 -0
  40. aweb_naapp-0.1.0/src/aweb_naapp/egress/send.py +359 -0
  41. aweb_naapp-0.1.0/src/aweb_naapp/egress/urls.py +500 -0
  42. aweb_naapp-0.1.0/src/aweb_naapp/hooks/__init__.py +5 -0
  43. aweb_naapp-0.1.0/src/aweb_naapp/hooks/catalog.py +579 -0
  44. aweb_naapp-0.1.0/src/aweb_naapp/hooks/delivery.py +278 -0
  45. aweb_naapp-0.1.0/src/aweb_naapp/hooks/envelope.py +341 -0
  46. aweb_naapp-0.1.0/src/aweb_naapp/hooks/migrations/001_owner_act_nonces.sql +19 -0
  47. aweb_naapp-0.1.0/src/aweb_naapp/hooks/owner_act.py +529 -0
  48. aweb_naapp-0.1.0/src/aweb_naapp/hooks/owner_act_nonces.py +203 -0
  49. aweb_naapp-0.1.0/src/aweb_naapp/hooks/strictjson.py +207 -0
  50. aweb_naapp-0.1.0/src/aweb_naapp/idempotency/__init__.py +123 -0
  51. aweb_naapp-0.1.0/src/aweb_naapp/idempotency/migrations/001_idempotency_keys.sql +16 -0
  52. aweb_naapp-0.1.0/src/aweb_naapp/llms.py +74 -0
  53. aweb_naapp-0.1.0/src/aweb_naapp/manifest.py +101 -0
  54. aweb_naapp-0.1.0/src/aweb_naapp/ops/__init__.py +1 -0
  55. aweb_naapp-0.1.0/src/aweb_naapp/ops/__main__.py +100 -0
  56. aweb_naapp-0.1.0/src/aweb_naapp/ops/render.py +487 -0
  57. aweb_naapp-0.1.0/src/aweb_naapp/outbox/__init__.py +701 -0
  58. aweb_naapp-0.1.0/src/aweb_naapp/outbox/migrations/001_outbox.sql +59 -0
  59. aweb_naapp-0.1.0/src/aweb_naapp/outbox/migrations/002_outbox_channels.sql +13 -0
  60. aweb_naapp-0.1.0/src/aweb_naapp/page_store/__init__.py +18 -0
  61. aweb_naapp-0.1.0/src/aweb_naapp/page_store/ledger_migrations/001_deletions.sql +10 -0
  62. aweb_naapp-0.1.0/src/aweb_naapp/page_store/ledger_migrations/002_request_claims.sql +8 -0
  63. aweb_naapp-0.1.0/src/aweb_naapp/page_store/lifecycle.py +459 -0
  64. aweb_naapp-0.1.0/src/aweb_naapp/page_store/migrations/001_pages.sql +52 -0
  65. aweb_naapp-0.1.0/src/aweb_naapp/page_store/migrations/002_import_history.sql +5 -0
  66. aweb_naapp-0.1.0/src/aweb_naapp/page_store/migrations/003_operator_policy.sql +3 -0
  67. aweb_naapp-0.1.0/src/aweb_naapp/page_store/migrations/004_layout_context.sql +8 -0
  68. aweb_naapp-0.1.0/src/aweb_naapp/page_store/migrations/005_restore_generation.sql +6 -0
  69. aweb_naapp-0.1.0/src/aweb_naapp/page_store/models.py +141 -0
  70. aweb_naapp-0.1.0/src/aweb_naapp/page_store/store.py +995 -0
  71. aweb_naapp-0.1.0/src/aweb_naapp/page_store/support.py +50 -0
  72. aweb_naapp-0.1.0/src/aweb_naapp/pages/__init__.py +36 -0
  73. aweb_naapp-0.1.0/src/aweb_naapp/pages/assets/gallery-entry.js +100 -0
  74. aweb_naapp-0.1.0/src/aweb_naapp/pages/assets/gallery-video.mp4 +0 -0
  75. aweb_naapp-0.1.0/src/aweb_naapp/pages/assets/page-renderer.css +764 -0
  76. aweb_naapp-0.1.0/src/aweb_naapp/pages/assets/page-renderer.js +9193 -0
  77. aweb_naapp-0.1.0/src/aweb_naapp/pages/errors.py +34 -0
  78. aweb_naapp-0.1.0/src/aweb_naapp/pages/gallery.py +396 -0
  79. aweb_naapp-0.1.0/src/aweb_naapp/pages/gallery_host.py +349 -0
  80. aweb_naapp-0.1.0/src/aweb_naapp/pages/lint.py +561 -0
  81. aweb_naapp-0.1.0/src/aweb_naapp/pages/page-format-v1.schema.json +1843 -0
  82. aweb_naapp-0.1.0/src/aweb_naapp/pages/render.py +82 -0
  83. aweb_naapp-0.1.0/src/aweb_naapp/pages/text.py +132 -0
  84. aweb_naapp-0.1.0/src/aweb_naapp/pages/validation.py +219 -0
  85. aweb_naapp-0.1.0/src/aweb_naapp/reference.py +336 -0
  86. aweb_naapp-0.1.0/src/aweb_naapp/robots.py +12 -0
  87. aweb_naapp-0.1.0/src/aweb_naapp/roles/__init__.py +203 -0
  88. aweb_naapp-0.1.0/src/aweb_naapp/roles/migrations/001_role_assignments.sql +18 -0
  89. aweb_naapp-0.1.0/src/aweb_naapp/runtime/__init__.py +1 -0
  90. aweb_naapp-0.1.0/src/aweb_naapp/runtime/app.py +436 -0
  91. aweb_naapp-0.1.0/src/aweb_naapp/runtime/app_emit.py +109 -0
  92. aweb_naapp-0.1.0/src/aweb_naapp/runtime/app_keys.py +95 -0
  93. aweb_naapp-0.1.0/src/aweb_naapp/runtime/approvals/__init__.py +76 -0
  94. aweb_naapp-0.1.0/src/aweb_naapp/runtime/approvals/challenge.py +32 -0
  95. aweb_naapp-0.1.0/src/aweb_naapp/runtime/approvals/migrations/001_approvals.sql +67 -0
  96. aweb_naapp-0.1.0/src/aweb_naapp/runtime/approvals/model.py +320 -0
  97. aweb_naapp-0.1.0/src/aweb_naapp/runtime/approvals/request.py +87 -0
  98. aweb_naapp-0.1.0/src/aweb_naapp/runtime/approvals/service.py +750 -0
  99. aweb_naapp-0.1.0/src/aweb_naapp/runtime/approvals/store.py +440 -0
  100. aweb_naapp-0.1.0/src/aweb_naapp/runtime/attestation.py +258 -0
  101. aweb_naapp-0.1.0/src/aweb_naapp/runtime/audit.py +49 -0
  102. aweb_naapp-0.1.0/src/aweb_naapp/runtime/authorization.py +219 -0
  103. aweb_naapp-0.1.0/src/aweb_naapp/runtime/build_identity.py +112 -0
  104. aweb_naapp-0.1.0/src/aweb_naapp/runtime/db.py +48 -0
  105. aweb_naapp-0.1.0/src/aweb_naapp/runtime/event_emit.py +220 -0
  106. aweb_naapp-0.1.0/src/aweb_naapp/runtime/freshness.py +94 -0
  107. aweb_naapp-0.1.0/src/aweb_naapp/runtime/health.py +120 -0
  108. aweb_naapp-0.1.0/src/aweb_naapp/runtime/links.py +34 -0
  109. aweb_naapp-0.1.0/src/aweb_naapp/runtime/migrations/001_observed_principals.sql +20 -0
  110. aweb_naapp-0.1.0/src/aweb_naapp/runtime/origins.py +121 -0
  111. aweb_naapp-0.1.0/src/aweb_naapp/runtime/passkeys/__init__.py +53 -0
  112. aweb_naapp-0.1.0/src/aweb_naapp/runtime/passkeys/migrations/001_passkeys.sql +96 -0
  113. aweb_naapp-0.1.0/src/aweb_naapp/runtime/passkeys/rp.py +33 -0
  114. aweb_naapp-0.1.0/src/aweb_naapp/runtime/passkeys/service.py +1151 -0
  115. aweb_naapp-0.1.0/src/aweb_naapp/runtime/passkeys/webauthn.py +90 -0
  116. aweb_naapp-0.1.0/src/aweb_naapp/runtime/settings.py +77 -0
  117. aweb_naapp-0.1.0/src/aweb_naapp/runtime/team_auth.py +616 -0
  118. aweb_naapp-0.1.0/src/aweb_naapp/runtime/team_server.py +170 -0
  119. aweb_naapp-0.1.0/src/aweb_naapp/signing_key.py +82 -0
  120. aweb_naapp-0.1.0/src/aweb_naapp/skills.py +99 -0
  121. aweb_naapp-0.1.0/testing/pyproject.toml +25 -0
  122. aweb_naapp-0.1.0/testing/src/aweb_naapp_testing/__init__.py +215 -0
  123. aweb_naapp-0.1.0/testing/src/aweb_naapp_testing/network.py +271 -0
  124. aweb_naapp-0.1.0/testing/src/aweb_naapp_testing/owner_acts.py +102 -0
  125. aweb_naapp-0.1.0/testing/src/aweb_naapp_testing/webauthn.py +166 -0
  126. aweb_naapp-0.1.0/tests/__init__.py +0 -0
  127. aweb_naapp-0.1.0/tests/conftest.py +3 -0
  128. aweb_naapp-0.1.0/tests/dispatch/__init__.py +0 -0
  129. aweb_naapp-0.1.0/tests/dispatch/test_dispatch.py +606 -0
  130. aweb_naapp-0.1.0/tests/dispatch/test_dispatch_is_dependency_light.py +31 -0
  131. aweb_naapp-0.1.0/tests/egress/__init__.py +0 -0
  132. aweb_naapp-0.1.0/tests/egress/test_addresses.py +91 -0
  133. aweb_naapp-0.1.0/tests/egress/test_egress_is_dependency_light.py +34 -0
  134. aweb_naapp-0.1.0/tests/egress/test_send.py +562 -0
  135. aweb_naapp-0.1.0/tests/egress/test_url_vectors.py +222 -0
  136. aweb_naapp-0.1.0/tests/hooks/__init__.py +0 -0
  137. aweb_naapp-0.1.0/tests/hooks/conftest.py +3 -0
  138. aweb_naapp-0.1.0/tests/hooks/test_binding_cases.py +159 -0
  139. aweb_naapp-0.1.0/tests/hooks/test_catalog.py +211 -0
  140. aweb_naapp-0.1.0/tests/hooks/test_delivery.py +135 -0
  141. aweb_naapp-0.1.0/tests/hooks/test_envelope.py +87 -0
  142. aweb_naapp-0.1.0/tests/hooks/test_envelope_vector_payloads.py +50 -0
  143. aweb_naapp-0.1.0/tests/hooks/test_hooks_is_dependency_free.py +58 -0
  144. aweb_naapp-0.1.0/tests/hooks/test_owner_act_signing.py +123 -0
  145. aweb_naapp-0.1.0/tests/hooks/test_owner_act_vector.py +127 -0
  146. aweb_naapp-0.1.0/tests/hooks/test_owner_act_verifier.py +295 -0
  147. aweb_naapp-0.1.0/tests/idempotency/__init__.py +0 -0
  148. aweb_naapp-0.1.0/tests/idempotency/test_idempotency.py +183 -0
  149. aweb_naapp-0.1.0/tests/idempotency/test_idempotency_is_dependency_light.py +31 -0
  150. aweb_naapp-0.1.0/tests/outbox/__init__.py +0 -0
  151. aweb_naapp-0.1.0/tests/outbox/drain_child.py +41 -0
  152. aweb_naapp-0.1.0/tests/outbox/test_outbox.py +729 -0
  153. aweb_naapp-0.1.0/tests/outbox/test_outbox_is_dependency_light.py +31 -0
  154. aweb_naapp-0.1.0/tests/page_store/__init__.py +0 -0
  155. aweb_naapp-0.1.0/tests/page_store/conftest.py +2 -0
  156. aweb_naapp-0.1.0/tests/page_store/test_layout_state.py +89 -0
  157. aweb_naapp-0.1.0/tests/page_store/test_lifecycle.py +400 -0
  158. aweb_naapp-0.1.0/tests/page_store/test_signed_runtime.py +179 -0
  159. aweb_naapp-0.1.0/tests/page_store/test_store.py +465 -0
  160. aweb_naapp-0.1.0/tests/pages/__init__.py +0 -0
  161. aweb_naapp-0.1.0/tests/pages/test_page_format_vectors.py +203 -0
  162. aweb_naapp-0.1.0/tests/pages/test_page_proposal_vectors.py +40 -0
  163. aweb_naapp-0.1.0/tests/pages/test_render.py +33 -0
  164. aweb_naapp-0.1.0/tests/roles/__init__.py +0 -0
  165. aweb_naapp-0.1.0/tests/roles/test_roles.py +298 -0
  166. aweb_naapp-0.1.0/tests/roles/test_roles_is_dependency_light.py +32 -0
  167. aweb_naapp-0.1.0/tests/runtime/__init__.py +0 -0
  168. aweb_naapp-0.1.0/tests/runtime/approvals/__init__.py +0 -0
  169. aweb_naapp-0.1.0/tests/runtime/approvals/conftest.py +13 -0
  170. aweb_naapp-0.1.0/tests/runtime/approvals/support.py +273 -0
  171. aweb_naapp-0.1.0/tests/runtime/approvals/test_approval_request.py +111 -0
  172. aweb_naapp-0.1.0/tests/runtime/approvals/test_approvals.py +1000 -0
  173. aweb_naapp-0.1.0/tests/runtime/approvals/test_links.py +29 -0
  174. aweb_naapp-0.1.0/tests/runtime/approvals/test_passkeys.py +1206 -0
  175. aweb_naapp-0.1.0/tests/runtime/approvals/test_webauthn.py +192 -0
  176. aweb_naapp-0.1.0/tests/runtime/conftest.py +70 -0
  177. aweb_naapp-0.1.0/tests/runtime/test_authorization.py +352 -0
  178. aweb_naapp-0.1.0/tests/runtime/test_awid_team_cache.py +334 -0
  179. aweb_naapp-0.1.0/tests/runtime/test_did_aw_current_key.py +242 -0
  180. aweb_naapp-0.1.0/tests/runtime/test_freshness.py +283 -0
  181. aweb_naapp-0.1.0/tests/runtime/test_hostile_team_auth_headers.py +75 -0
  182. aweb_naapp-0.1.0/tests/runtime/test_naapp_runtime.py +451 -0
  183. aweb_naapp-0.1.0/tests/runtime/test_observed_principals.py +63 -0
  184. aweb_naapp-0.1.0/tests/runtime/test_origins.py +186 -0
  185. aweb_naapp-0.1.0/tests/runtime/test_principal_attestation.py +251 -0
  186. aweb_naapp-0.1.0/tests/runtime/test_runtime_health.py +93 -0
  187. aweb_naapp-0.1.0/tests/runtime/test_team_auth_envelope.py +259 -0
  188. aweb_naapp-0.1.0/tests/runtime/test_team_auth_vectors.py +85 -0
  189. aweb_naapp-0.1.0/tests/runtime/test_team_server_directory.py +189 -0
  190. aweb_naapp-0.1.0/tests/test_app_emit_credential.py +92 -0
  191. aweb_naapp-0.1.0/tests/test_app_keys.py +187 -0
  192. aweb_naapp-0.1.0/tests/test_db.py +148 -0
  193. aweb_naapp-0.1.0/tests/test_docs_surface_is_dependency_free.py +33 -0
  194. aweb_naapp-0.1.0/tests/test_event_emit.py +444 -0
  195. aweb_naapp-0.1.0/tests/test_health.py +279 -0
  196. aweb_naapp-0.1.0/tests/test_manifest_file.py +65 -0
  197. aweb_naapp-0.1.0/tests/test_naapp.py +360 -0
  198. aweb_naapp-0.1.0/tests/test_ops_render.py +458 -0
  199. aweb_naapp-0.1.0/tests/test_routes.py +274 -0
  200. aweb_naapp-0.1.0/tests/test_settings.py +120 -0
  201. aweb_naapp-0.1.0/tests/test_signing_key.py +151 -0
  202. aweb_naapp-0.1.0/tests/test_skills.py +98 -0
  203. aweb_naapp-0.1.0/uv.lock +1797 -0
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .coverage
8
+ .venv/
9
+ dist/
10
+ build/
11
+ .DS_Store
12
+
13
+ # Editor lock/autosave files
14
+ .#*
15
+ \#*\#
16
+ *~
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.5
2
+ Name: aweb-naapp
3
+ Version: 0.1.0
4
+ Summary: Reusable Native Agentic App (naapp) docs surface: shared chrome, the aweb design system, and manifest-driven llms.txt and reference generators.
5
+ Project-URL: Homepage, https://github.com/awebai/aweb/tree/main/naapp-lib
6
+ Project-URL: Repository, https://github.com/awebai/aweb
7
+ Project-URL: Issues, https://github.com/awebai/aweb/issues
8
+ Author-email: "aweb.ai" <hello@aweb.ai>
9
+ License: MIT
10
+ Keywords: agents,aweb,awid,manifest,naapp
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.12
18
+ Provides-Extra: dispatch
19
+ Requires-Dist: pgdbm>=0.5.0; extra == 'dispatch'
20
+ Provides-Extra: egress
21
+ Requires-Dist: dnspython>=2.7.0; extra == 'egress'
22
+ Requires-Dist: httpcore[asyncio]>=1.0.9; extra == 'egress'
23
+ Requires-Dist: idna>=3.10; extra == 'egress'
24
+ Requires-Dist: publicsuffixlist==1.1.0.20261018; extra == 'egress'
25
+ Provides-Extra: hooks
26
+ Requires-Dist: awid-service>=0.5.30; extra == 'hooks'
27
+ Requires-Dist: pgdbm>=0.5.0; extra == 'hooks'
28
+ Requires-Dist: pynacl>=1.6.2; extra == 'hooks'
29
+ Provides-Extra: idempotency
30
+ Requires-Dist: pgdbm>=0.5.0; extra == 'idempotency'
31
+ Provides-Extra: outbox
32
+ Requires-Dist: pgdbm>=0.5.0; extra == 'outbox'
33
+ Provides-Extra: page-store
34
+ Requires-Dist: jsonschema>=4.23; extra == 'page-store'
35
+ Requires-Dist: pgdbm>=0.5.0; extra == 'page-store'
36
+ Provides-Extra: pages
37
+ Requires-Dist: jsonschema>=4.23; extra == 'pages'
38
+ Provides-Extra: roles
39
+ Requires-Dist: pgdbm>=0.5.0; extra == 'roles'
40
+ Provides-Extra: runtime
41
+ Requires-Dist: awid-service>=0.5.30; extra == 'runtime'
42
+ Requires-Dist: fastapi>=0.116.1; extra == 'runtime'
43
+ Requires-Dist: httpx>=0.28.1; extra == 'runtime'
44
+ Requires-Dist: pgdbm>=0.5.0; extra == 'runtime'
45
+ Requires-Dist: pydantic-settings>=2.10.1; extra == 'runtime'
46
+ Requires-Dist: pynacl>=1.6.2; extra == 'runtime'
47
+ Requires-Dist: webauthn>=3.0.1; extra == 'runtime'
48
+ Provides-Extra: signing-key
49
+ Requires-Dist: pynacl>=1.6.2; extra == 'signing-key'
50
+ Description-Content-Type: text/markdown
51
+
52
+ # aweb-naapp
53
+
54
+ The reusable Native Agentic App (naapp) docs surface. Every naapp built with it
55
+ serves the same standard surface — so the standard is shared code, not a doc
56
+ copied by hand.
57
+
58
+ An app supplies its canonical `aweb-app.json` manifest, a `SiteConfig` (brand,
59
+ nav, footer), and its own landing body, and gets:
60
+
61
+ - the **aweb design system** (Paper/Clay), vendored and sha-pinned, served at
62
+ `/css/aweb.css`;
63
+ - the **shared chrome** — head, header, footer, theme toggle, and copy button;
64
+ - **manifest-driven llms.txt blocks** — the operations list and the
65
+ team-certificate authentication section;
66
+ - the **`/reference` page** — every operation in parallel as the canonical
67
+ `aw <verb>` command and the raw HTTP wire format, with the honest team-cert v2
68
+ envelope linked to the conformance vector.
69
+
70
+ ```python
71
+ import aweb_naapp as naapp
72
+
73
+ site = naapp.SiteConfig(
74
+ origin="https://library.aweb.ai",
75
+ brand="library",
76
+ title="library — agent profiles for AWID teams",
77
+ description="...",
78
+ nav_links=(naapp.NavLink("Reference", "/reference"), ...),
79
+ footer_blurb="...",
80
+ footer_columns=(...),
81
+ footer_bottom="library is a Native Agentic App on the aweb.ai hub. AWID is the identity authority.",
82
+ )
83
+
84
+ reference_html = naapp.render_reference(manifest, site, verb="library")
85
+ landing_html = naapp.page(site, body_html, include_copy_llms=True)
86
+ css_bytes = naapp.aweb_css()
87
+ ```
88
+
89
+ The docs surface is pure standard library. Request-time runtime code (the
90
+ team-auth verifier, approvals, passkeys, and team-server discovery from the
91
+ team's AWID `aweb.team-server/1` record) lives under `aweb_naapp.runtime` and
92
+ needs the `runtime` extra: `aweb-naapp[runtime]`. Two parts the core server
93
+ shares import without it: the durable outbox, `aweb_naapp.outbox`
94
+ (`aweb-naapp[outbox]`), and the signing-key reader, `aweb_naapp.signing_key`
95
+ (`aweb-naapp[signing-key]`).
96
+
97
+ The egress guard, `aweb_naapp.egress`, is for naapps that call an external
98
+ service with their own credential. It needs only the `egress` extra,
99
+ `aweb-naapp[egress]`, and none of the runtime. It canonicalizes and matches
100
+ outbound URLs under the contracts in `docs/vectors/egress-url-v2.json` and
101
+ `docs/vectors/egress-address-v1.json`, resolves names itself and refuses any
102
+ denied address, and sends a request only to the destination it matches,
103
+ through one pinned, verified connection. Redirects come back as responses, no proxy is read from
104
+ the environment, and a failure is classed as not sent or of unknown outcome.
105
+
106
+ ### App keys
107
+
108
+ An app names its signing keys by kid, as key files:
109
+ `<PREFIX>APP_KEY_FILES='{"<kid>": "/run/secrets/<file>"}'`. A key file holds the
110
+ 32-byte Ed25519 seed as 64 hex characters, with one trailing newline allowed. It
111
+ must be a regular file of at most 1 KiB, and must not be group- or world-writable. The manifest
112
+ declares which kid signs which purpose, and the app does not start unless the
113
+ configured key is the declared one. An app with `<PREFIX>APP_EVENTS_ORIGIN` set
114
+ (https, or http on loopback) must hold its declared app-event key. That key may instead be given as
115
+ `<PREFIX>APP_EMIT_KEY_SEED_HEX` with `<PREFIX>APP_EMIT_KID`, a form that signs
116
+ app events only.
117
+
118
+ The egress guard, `aweb_naapp.egress`, is for naapps that call an external
119
+ service with their own credential. It needs only the `egress` extra,
120
+ `aweb-naapp[egress]`, and none of the runtime. It canonicalizes and matches
121
+ outbound URLs under the contracts in `docs/vectors/egress-url-v2.json` and
122
+ `docs/vectors/egress-address-v1.json`, resolves names itself and refuses any
123
+ denied address, and sends a request only to the destination it matches,
124
+ through one pinned, verified connection. Redirects come back as responses, no proxy is read from
125
+ the environment, and a failure is classed as not sent or of unknown outcome.
126
+
127
+ ### Health
128
+
129
+ `/live` reports the app's id and build commit. `/health` and `/ready` also run
130
+ the app's dependency checks and report each by name: `ready` is 503 when any
131
+ check fails, and `health` always answers 200. The runtime checks its database
132
+ and reports the age of its oldest undelivered outbox event
133
+ (`NaappRuntime.health_checks()`). An app adds its own as async callables that
134
+ return a `CheckResult`. A check that raises, or does not answer within 5 s,
135
+ fails, and its error is logged, never served. `/ready` serves only whether each
136
+ check passed; `/health` adds each check's detail, whose values are numbers,
137
+ booleans or null, never text. The routes are unauthenticated,
138
+ so the checks run at most once every 10 s: concurrent requests share the run in
139
+ flight, and later ones get its result until it expires.
140
+
141
+ ### Agent and human origins
142
+
143
+ An app with pages for people in a browser serves them on their own origin.
144
+ `aweb_naapp.runtime.origins.split_origins(agent_app, human_app, agent_origin=...,
145
+ human_origin=...)` is one ASGI app that dispatches on `Host`:
146
+
147
+ - the agent origin serves the runtime's app, with no cookies in either
148
+ direction, so its only authority is the team-auth signature;
149
+ - the human origin refuses any request carrying `Authorization`, `X-AWEB-*` or
150
+ `X-AWID-*` (`403 team_auth_refused`);
151
+ - any other host is `421 unknown_origin`.
152
+
153
+ A route exists on one origin because it is declared in one of the two apps. The
154
+ lifespan is the agent app's.
155
+
156
+ ## Approvals and passkeys
157
+
158
+ `aweb_naapp.runtime.approvals` is the single-use human approval primitive that
159
+ apps share. An agent proposes an exact request; the owner-designated human
160
+ approves or rejects it; a trusted dispatcher then admits exactly one dispatch of
161
+ that exact request.
162
+
163
+ - **Frozen request.** `request_sha256` is SHA-256 over
164
+ `aweb.approval-request/1\n` plus the canonical JSON of the request and its
165
+ policy reference and version. Changing any covered value needs a new approval.
166
+ `parse_request_json` refuses duplicate keys, floats and non-finite numbers.
167
+ - **Who decides.** The app's `ApproverDirectory` names the owner-designated
168
+ approver for the policy; the requester never chooses the approver, the
169
+ contact or the delivery. The requester sees only `RequesterView`
170
+ (`request_id`, state, delivery status, outcome): never a link, token or
171
+ capability.
172
+ - **Presentation.** A link presentation sends a one-use link to the approver's
173
+ registered contact through the app's `ApprovalDelivery`; only its SHA-256
174
+ verifier is stored. The link only locates the request. Opening it consumes
175
+ nothing; a successful decision uses it. Under a passkey-minimum policy the
176
+ request is shown only after a `view` assertion, which consumes nothing;
177
+ otherwise, or when the policy sets `link_reveals_request`, link possession
178
+ alone reveals the request. A task presentation needs the app's
179
+ `TaskAnswerVerifier` to verify the member-signed answer and its receipt, and
180
+ the authenticator proof is bound to that task answer; the approval stays
181
+ pending until both verify.
182
+ - **Assurance.** Each `ActionPolicy` sets a minimum of `member_session` or
183
+ `passkey`. An `Authenticator` adapter derives the evidence from a verified
184
+ proof; a caller cannot claim an assurance by naming it. The passkey adapter is
185
+ below; `member_session` is an adapter seam for apps that have member sessions.
186
+ Every proof is bound by `challenge_binding` to one approval, purpose
187
+ (approve, reject or view) and task answer.
188
+ - **Current approver.** An approval is only as good as its approver's current
189
+ designation: challenge, decide and admit refuse with `approver_retired` once
190
+ the `ApproverDirectory` no longer names the approver.
191
+ - **Admission.** `admit()` is a compare-and-set under a row lock and a lock per
192
+ request digest. A retry under the same operation ID retrieves the admission
193
+ and is not a new dispatch; after `not_sent` it allows a transport retry while
194
+ the approval is unexpired and unrevoked. The outcome lives on the consumption:
195
+ `dispatching → completed | not_sent | outcome_unknown`, and the owner
196
+ reconciles `outcome_unknown`. While a dispatch of the same request digest is
197
+ dispatching or unknown, no other approval of it is admitted. No outcome rearms
198
+ an approval.
199
+ - **Transactions.** `decide`, `admit`, `record_outcome`, `reconcile`, `revoke`
200
+ and `revoke_many` take an optional pgdbm transaction from the same database,
201
+ so the change commits with the app's own writes. Audit events go to the app's
202
+ `AuditSink` inside the transaction of the change they record.
203
+ - **Storage.** `ApprovalStore` is the interface; `PgApprovalStore(db)` keeps
204
+ the records in the app's own pgdbm schema, and `migrate()` applies its
205
+ migrations (module `aweb_naapp_approvals`).
206
+
207
+ `aweb_naapp.runtime.passkeys` verifies app-local WebAuthn passkeys with
208
+ [py_webauthn](https://github.com/duo-labs/py_webauthn), maintained by Duo Labs
209
+ since 2017. It does all of the WebAuthn cryptography and
210
+ parsing: attestation formats, COSE keys, signature and counter checks. Its
211
+ server-only API takes the expected challenge, relying party ID, origin and
212
+ user-verification requirement as explicit arguments, which this module binds to
213
+ the approval, decision, policy and the app's own origin.
214
+
215
+ - The relying party is the app's own origin (`RelyingParty`).
216
+ - `verify_registration(credential, challenge=..., rp=...)` and
217
+ `verify_assertion(proof, challenge=..., rp=..., credential_id=...,
218
+ public_key=..., sign_count=...)` are the stateless ceremony checks `Passkeys`
219
+ uses, for any caller with its own challenges and credentials. They always
220
+ require user verification and raise `WebAuthnRefused` on any failure.
221
+ `verify_assertion` takes the stored credential the caller chose for the
222
+ expected subject and refuses a proof naming any other credential, since the
223
+ proof's credential ID is not signed. The caller makes each challenge
224
+ single-use, stores the credential and advances its counter with a
225
+ compare-and-set on `new_sign_count`.
226
+ - `designate(tx, scope=..., contact=..., evidence=...)` runs in the caller's
227
+ transaction. The evidence is a `VerifiedOwnerAct`, which the owner-act
228
+ verifier (`aweb_naapp.hooks.owner_act_nonces`) consumed in that same
229
+ transaction: `app-role.designate` for a scope's first approver,
230
+ `app-credential.recover` to replace one. The act names the scope, commits to
231
+ the contact by hash and takes effect once. For a scope the app does not count
232
+ as an owner scope (`Passkeys(owner_scopes=...)`, every scope by default), the
233
+ evidence may instead be an `OwnerApproval` from `owner_approval()`: an owner's
234
+ decided approval, admitted and completed through B2 in the same transaction.
235
+ It designates only a scope's first approver. Whether the approved request asks
236
+ for that designation is the app's check.
237
+ - Nothing is sent inside the transaction. After the commit the caller sends the
238
+ enrolment link and any recovery notices with `deliver(designation)`. If that
239
+ never happens, `reissue_enrolment(tx, scope=...)` revokes the unused link and
240
+ issues a new one to the same contact, while the approver has not enrolled.
241
+ - The enrolment link goes only to the designated contact. Whoever first
242
+ completes enrolment through the link registers the passkey: this is **trust
243
+ on first use**, and the audit event records it as such. The link is as strong
244
+ as the contact channel it travels through.
245
+ - Recovery from a lockout retires the previous approver at once, with its
246
+ passkeys, unused enrolment links and live approvals (through
247
+ `ApprovalRevocation`, in the same transaction). The new approver takes effect
248
+ after a cooling-off period (`LockoutPolicy`, at least 72 h), during which
249
+ the scope has no approver, and every contact previously recorded for the
250
+ scope is notified. `recovery_pending_until` tells the app to freeze its own
251
+ widening changes meanwhile.
252
+ - `retire(tx, scope=..., evidence=...)` ends a scope's designation on an
253
+ `app-role.revoke` act consumed in the same transaction, for instance when the
254
+ owner role behind the scope is revoked. The act's `subject_ref` must name the
255
+ approver retire finds current, so a recovery committed after the act was
256
+ signed is never retired by it. The current approver, or a recovery's
257
+ pending one, is retired with its passkeys, unused enrolment links and live
258
+ approvals. The scope then has no approver: its next designation is a first
259
+ one and enrols a new passkey, and the retired passkeys stay dead.
260
+ - A contact change needs the approver's own passkey, takes effect after a delay
261
+ (at least 24 h) and is notified to the old and the new contact.
262
+ - These durations are floors. App or owner policy may lengthen them, and
263
+ deploy configuration may raise the floor itself (`lockout_floor`); nothing
264
+ can go below 72 h and 24 h.
265
+ - Assertions are bound to one approval, purpose, task answer, request digest
266
+ and policy version through `challenge_binding`. They need user verification, are
267
+ single-use, and the authenticator counter must advance.
268
+ - Designation is the owner's attestation of one natural person. The primitive
269
+ cannot detect sharing: a passkey or contact that several people share makes
270
+ every approval attributable only to the group. Every `Authenticator` must
271
+ likewise refuse non-individual principals; a `member_session` adapter needs a
272
+ per-member principal from the identity layer or a custody attestation.
273
+ - `login_options(scope)` / `verify_login(scope, proof)` log in a scope's
274
+ in-effect approver under the same rules, returning `PasskeyLogin(subject,
275
+ contact, evidence_ref)`. What a login opens (a console or reader session) is
276
+ the caller's. Roles are named scopes, such as `secrets.owner`, each designated
277
+ through a team owner act.
278
+ - A passkey proves that an app-local human holds the enrolled authenticator. It
279
+ is not AWID membership, team identity or key custody.
@@ -0,0 +1,228 @@
1
+ # aweb-naapp
2
+
3
+ The reusable Native Agentic App (naapp) docs surface. Every naapp built with it
4
+ serves the same standard surface — so the standard is shared code, not a doc
5
+ copied by hand.
6
+
7
+ An app supplies its canonical `aweb-app.json` manifest, a `SiteConfig` (brand,
8
+ nav, footer), and its own landing body, and gets:
9
+
10
+ - the **aweb design system** (Paper/Clay), vendored and sha-pinned, served at
11
+ `/css/aweb.css`;
12
+ - the **shared chrome** — head, header, footer, theme toggle, and copy button;
13
+ - **manifest-driven llms.txt blocks** — the operations list and the
14
+ team-certificate authentication section;
15
+ - the **`/reference` page** — every operation in parallel as the canonical
16
+ `aw <verb>` command and the raw HTTP wire format, with the honest team-cert v2
17
+ envelope linked to the conformance vector.
18
+
19
+ ```python
20
+ import aweb_naapp as naapp
21
+
22
+ site = naapp.SiteConfig(
23
+ origin="https://library.aweb.ai",
24
+ brand="library",
25
+ title="library — agent profiles for AWID teams",
26
+ description="...",
27
+ nav_links=(naapp.NavLink("Reference", "/reference"), ...),
28
+ footer_blurb="...",
29
+ footer_columns=(...),
30
+ footer_bottom="library is a Native Agentic App on the aweb.ai hub. AWID is the identity authority.",
31
+ )
32
+
33
+ reference_html = naapp.render_reference(manifest, site, verb="library")
34
+ landing_html = naapp.page(site, body_html, include_copy_llms=True)
35
+ css_bytes = naapp.aweb_css()
36
+ ```
37
+
38
+ The docs surface is pure standard library. Request-time runtime code (the
39
+ team-auth verifier, approvals, passkeys, and team-server discovery from the
40
+ team's AWID `aweb.team-server/1` record) lives under `aweb_naapp.runtime` and
41
+ needs the `runtime` extra: `aweb-naapp[runtime]`. Two parts the core server
42
+ shares import without it: the durable outbox, `aweb_naapp.outbox`
43
+ (`aweb-naapp[outbox]`), and the signing-key reader, `aweb_naapp.signing_key`
44
+ (`aweb-naapp[signing-key]`).
45
+
46
+ The egress guard, `aweb_naapp.egress`, is for naapps that call an external
47
+ service with their own credential. It needs only the `egress` extra,
48
+ `aweb-naapp[egress]`, and none of the runtime. It canonicalizes and matches
49
+ outbound URLs under the contracts in `docs/vectors/egress-url-v2.json` and
50
+ `docs/vectors/egress-address-v1.json`, resolves names itself and refuses any
51
+ denied address, and sends a request only to the destination it matches,
52
+ through one pinned, verified connection. Redirects come back as responses, no proxy is read from
53
+ the environment, and a failure is classed as not sent or of unknown outcome.
54
+
55
+ ### App keys
56
+
57
+ An app names its signing keys by kid, as key files:
58
+ `<PREFIX>APP_KEY_FILES='{"<kid>": "/run/secrets/<file>"}'`. A key file holds the
59
+ 32-byte Ed25519 seed as 64 hex characters, with one trailing newline allowed. It
60
+ must be a regular file of at most 1 KiB, and must not be group- or world-writable. The manifest
61
+ declares which kid signs which purpose, and the app does not start unless the
62
+ configured key is the declared one. An app with `<PREFIX>APP_EVENTS_ORIGIN` set
63
+ (https, or http on loopback) must hold its declared app-event key. That key may instead be given as
64
+ `<PREFIX>APP_EMIT_KEY_SEED_HEX` with `<PREFIX>APP_EMIT_KID`, a form that signs
65
+ app events only.
66
+
67
+ The egress guard, `aweb_naapp.egress`, is for naapps that call an external
68
+ service with their own credential. It needs only the `egress` extra,
69
+ `aweb-naapp[egress]`, and none of the runtime. It canonicalizes and matches
70
+ outbound URLs under the contracts in `docs/vectors/egress-url-v2.json` and
71
+ `docs/vectors/egress-address-v1.json`, resolves names itself and refuses any
72
+ denied address, and sends a request only to the destination it matches,
73
+ through one pinned, verified connection. Redirects come back as responses, no proxy is read from
74
+ the environment, and a failure is classed as not sent or of unknown outcome.
75
+
76
+ ### Health
77
+
78
+ `/live` reports the app's id and build commit. `/health` and `/ready` also run
79
+ the app's dependency checks and report each by name: `ready` is 503 when any
80
+ check fails, and `health` always answers 200. The runtime checks its database
81
+ and reports the age of its oldest undelivered outbox event
82
+ (`NaappRuntime.health_checks()`). An app adds its own as async callables that
83
+ return a `CheckResult`. A check that raises, or does not answer within 5 s,
84
+ fails, and its error is logged, never served. `/ready` serves only whether each
85
+ check passed; `/health` adds each check's detail, whose values are numbers,
86
+ booleans or null, never text. The routes are unauthenticated,
87
+ so the checks run at most once every 10 s: concurrent requests share the run in
88
+ flight, and later ones get its result until it expires.
89
+
90
+ ### Agent and human origins
91
+
92
+ An app with pages for people in a browser serves them on their own origin.
93
+ `aweb_naapp.runtime.origins.split_origins(agent_app, human_app, agent_origin=...,
94
+ human_origin=...)` is one ASGI app that dispatches on `Host`:
95
+
96
+ - the agent origin serves the runtime's app, with no cookies in either
97
+ direction, so its only authority is the team-auth signature;
98
+ - the human origin refuses any request carrying `Authorization`, `X-AWEB-*` or
99
+ `X-AWID-*` (`403 team_auth_refused`);
100
+ - any other host is `421 unknown_origin`.
101
+
102
+ A route exists on one origin because it is declared in one of the two apps. The
103
+ lifespan is the agent app's.
104
+
105
+ ## Approvals and passkeys
106
+
107
+ `aweb_naapp.runtime.approvals` is the single-use human approval primitive that
108
+ apps share. An agent proposes an exact request; the owner-designated human
109
+ approves or rejects it; a trusted dispatcher then admits exactly one dispatch of
110
+ that exact request.
111
+
112
+ - **Frozen request.** `request_sha256` is SHA-256 over
113
+ `aweb.approval-request/1\n` plus the canonical JSON of the request and its
114
+ policy reference and version. Changing any covered value needs a new approval.
115
+ `parse_request_json` refuses duplicate keys, floats and non-finite numbers.
116
+ - **Who decides.** The app's `ApproverDirectory` names the owner-designated
117
+ approver for the policy; the requester never chooses the approver, the
118
+ contact or the delivery. The requester sees only `RequesterView`
119
+ (`request_id`, state, delivery status, outcome): never a link, token or
120
+ capability.
121
+ - **Presentation.** A link presentation sends a one-use link to the approver's
122
+ registered contact through the app's `ApprovalDelivery`; only its SHA-256
123
+ verifier is stored. The link only locates the request. Opening it consumes
124
+ nothing; a successful decision uses it. Under a passkey-minimum policy the
125
+ request is shown only after a `view` assertion, which consumes nothing;
126
+ otherwise, or when the policy sets `link_reveals_request`, link possession
127
+ alone reveals the request. A task presentation needs the app's
128
+ `TaskAnswerVerifier` to verify the member-signed answer and its receipt, and
129
+ the authenticator proof is bound to that task answer; the approval stays
130
+ pending until both verify.
131
+ - **Assurance.** Each `ActionPolicy` sets a minimum of `member_session` or
132
+ `passkey`. An `Authenticator` adapter derives the evidence from a verified
133
+ proof; a caller cannot claim an assurance by naming it. The passkey adapter is
134
+ below; `member_session` is an adapter seam for apps that have member sessions.
135
+ Every proof is bound by `challenge_binding` to one approval, purpose
136
+ (approve, reject or view) and task answer.
137
+ - **Current approver.** An approval is only as good as its approver's current
138
+ designation: challenge, decide and admit refuse with `approver_retired` once
139
+ the `ApproverDirectory` no longer names the approver.
140
+ - **Admission.** `admit()` is a compare-and-set under a row lock and a lock per
141
+ request digest. A retry under the same operation ID retrieves the admission
142
+ and is not a new dispatch; after `not_sent` it allows a transport retry while
143
+ the approval is unexpired and unrevoked. The outcome lives on the consumption:
144
+ `dispatching → completed | not_sent | outcome_unknown`, and the owner
145
+ reconciles `outcome_unknown`. While a dispatch of the same request digest is
146
+ dispatching or unknown, no other approval of it is admitted. No outcome rearms
147
+ an approval.
148
+ - **Transactions.** `decide`, `admit`, `record_outcome`, `reconcile`, `revoke`
149
+ and `revoke_many` take an optional pgdbm transaction from the same database,
150
+ so the change commits with the app's own writes. Audit events go to the app's
151
+ `AuditSink` inside the transaction of the change they record.
152
+ - **Storage.** `ApprovalStore` is the interface; `PgApprovalStore(db)` keeps
153
+ the records in the app's own pgdbm schema, and `migrate()` applies its
154
+ migrations (module `aweb_naapp_approvals`).
155
+
156
+ `aweb_naapp.runtime.passkeys` verifies app-local WebAuthn passkeys with
157
+ [py_webauthn](https://github.com/duo-labs/py_webauthn), maintained by Duo Labs
158
+ since 2017. It does all of the WebAuthn cryptography and
159
+ parsing: attestation formats, COSE keys, signature and counter checks. Its
160
+ server-only API takes the expected challenge, relying party ID, origin and
161
+ user-verification requirement as explicit arguments, which this module binds to
162
+ the approval, decision, policy and the app's own origin.
163
+
164
+ - The relying party is the app's own origin (`RelyingParty`).
165
+ - `verify_registration(credential, challenge=..., rp=...)` and
166
+ `verify_assertion(proof, challenge=..., rp=..., credential_id=...,
167
+ public_key=..., sign_count=...)` are the stateless ceremony checks `Passkeys`
168
+ uses, for any caller with its own challenges and credentials. They always
169
+ require user verification and raise `WebAuthnRefused` on any failure.
170
+ `verify_assertion` takes the stored credential the caller chose for the
171
+ expected subject and refuses a proof naming any other credential, since the
172
+ proof's credential ID is not signed. The caller makes each challenge
173
+ single-use, stores the credential and advances its counter with a
174
+ compare-and-set on `new_sign_count`.
175
+ - `designate(tx, scope=..., contact=..., evidence=...)` runs in the caller's
176
+ transaction. The evidence is a `VerifiedOwnerAct`, which the owner-act
177
+ verifier (`aweb_naapp.hooks.owner_act_nonces`) consumed in that same
178
+ transaction: `app-role.designate` for a scope's first approver,
179
+ `app-credential.recover` to replace one. The act names the scope, commits to
180
+ the contact by hash and takes effect once. For a scope the app does not count
181
+ as an owner scope (`Passkeys(owner_scopes=...)`, every scope by default), the
182
+ evidence may instead be an `OwnerApproval` from `owner_approval()`: an owner's
183
+ decided approval, admitted and completed through B2 in the same transaction.
184
+ It designates only a scope's first approver. Whether the approved request asks
185
+ for that designation is the app's check.
186
+ - Nothing is sent inside the transaction. After the commit the caller sends the
187
+ enrolment link and any recovery notices with `deliver(designation)`. If that
188
+ never happens, `reissue_enrolment(tx, scope=...)` revokes the unused link and
189
+ issues a new one to the same contact, while the approver has not enrolled.
190
+ - The enrolment link goes only to the designated contact. Whoever first
191
+ completes enrolment through the link registers the passkey: this is **trust
192
+ on first use**, and the audit event records it as such. The link is as strong
193
+ as the contact channel it travels through.
194
+ - Recovery from a lockout retires the previous approver at once, with its
195
+ passkeys, unused enrolment links and live approvals (through
196
+ `ApprovalRevocation`, in the same transaction). The new approver takes effect
197
+ after a cooling-off period (`LockoutPolicy`, at least 72 h), during which
198
+ the scope has no approver, and every contact previously recorded for the
199
+ scope is notified. `recovery_pending_until` tells the app to freeze its own
200
+ widening changes meanwhile.
201
+ - `retire(tx, scope=..., evidence=...)` ends a scope's designation on an
202
+ `app-role.revoke` act consumed in the same transaction, for instance when the
203
+ owner role behind the scope is revoked. The act's `subject_ref` must name the
204
+ approver retire finds current, so a recovery committed after the act was
205
+ signed is never retired by it. The current approver, or a recovery's
206
+ pending one, is retired with its passkeys, unused enrolment links and live
207
+ approvals. The scope then has no approver: its next designation is a first
208
+ one and enrols a new passkey, and the retired passkeys stay dead.
209
+ - A contact change needs the approver's own passkey, takes effect after a delay
210
+ (at least 24 h) and is notified to the old and the new contact.
211
+ - These durations are floors. App or owner policy may lengthen them, and
212
+ deploy configuration may raise the floor itself (`lockout_floor`); nothing
213
+ can go below 72 h and 24 h.
214
+ - Assertions are bound to one approval, purpose, task answer, request digest
215
+ and policy version through `challenge_binding`. They need user verification, are
216
+ single-use, and the authenticator counter must advance.
217
+ - Designation is the owner's attestation of one natural person. The primitive
218
+ cannot detect sharing: a passkey or contact that several people share makes
219
+ every approval attributable only to the group. Every `Authenticator` must
220
+ likewise refuse non-individual principals; a `member_session` adapter needs a
221
+ per-member principal from the identity layer or a custody attestation.
222
+ - `login_options(scope)` / `verify_login(scope, proof)` log in a scope's
223
+ in-effect approver under the same rules, returning `PasskeyLogin(subject,
224
+ contact, evidence_ref)`. What a login opens (a console or reader session) is
225
+ the caller's. Roles are named scopes, such as `secrets.owner`, each designated
226
+ through a team owner act.
227
+ - A passkey proves that an app-local human holds the enrolled authenticator. It
228
+ is not AWID membership, team identity or key custody.
@@ -0,0 +1,135 @@
1
+ [project]
2
+ name = "aweb-naapp"
3
+ version = "0.1.0"
4
+ description = "Reusable Native Agentic App (naapp) docs surface: shared chrome, the aweb design system, and manifest-driven llms.txt and reference generators."
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ authors = [{ name = "aweb.ai", email = "hello@aweb.ai" }]
8
+ requires-python = ">=3.12"
9
+ keywords = ["agents", "awid", "aweb", "naapp", "manifest"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ ]
18
+ dependencies = []
19
+
20
+ [project.optional-dependencies]
21
+ # Request-time runtime code under aweb_naapp.runtime. The docs
22
+ # surface (`import aweb_naapp`) never imports these.
23
+ runtime = [
24
+ # The first awid release with the principal-attestation records the runtime reads.
25
+ "awid-service>=0.5.30",
26
+ "fastapi>=0.116.1",
27
+ "httpx>=0.28.1",
28
+ "pgdbm>=0.5.0",
29
+ "pydantic-settings>=2.10.1",
30
+ "pynacl>=1.6.2",
31
+ "webauthn>=3.0.1",
32
+ ]
33
+ # The shared durable outbox, aweb_naapp.outbox: pgdbm only, so the core server
34
+ # can use it without the naapp runtime.
35
+ outbox = [
36
+ "pgdbm>=0.5.0",
37
+ ]
38
+ # The signing-key reader, aweb_naapp.signing_key: pynacl only, so the core
39
+ # server can read its keys the same way without the naapp runtime.
40
+ signing-key = [
41
+ "pynacl>=1.6.2",
42
+ ]
43
+ # The shared dispatch state machine, aweb_naapp.dispatch: pgdbm only.
44
+ dispatch = [
45
+ "pgdbm>=0.5.0",
46
+ ]
47
+ # The shared idempotency store, aweb_naapp.idempotency: pgdbm only.
48
+ idempotency = [
49
+ "pgdbm>=0.5.0",
50
+ ]
51
+ # Shared role assignments, aweb_naapp.roles: pgdbm only.
52
+ roles = [
53
+ "pgdbm>=0.5.0",
54
+ ]
55
+ # The shared page-format validator, aweb_naapp.pages: JSON Schema only, no I/O.
56
+ pages = [
57
+ "jsonschema>=4.23",
58
+ ]
59
+ # The shared egress guard, aweb_naapp.egress: URL canonicalization, its own
60
+ # resolver and a pinned verified connection, without the naapp runtime.
61
+ # publicsuffixlist is pinned exactly on purpose: the list decides which
62
+ # wildcard destinations are refused, so it changes only by a reviewed bump of
63
+ # this pin together with docs/vectors/egress-url-v2.json. Do not loosen it.
64
+ egress = [
65
+ "dnspython>=2.7.0",
66
+ "httpcore[asyncio]>=1.0.9",
67
+ "idna>=3.10",
68
+ "publicsuffixlist==1.1.0.20261018",
69
+ ]
70
+ # aw-hooks, aweb_naapp.hooks: the catalog reader (stdlib), the event envelope,
71
+ # the delivery credential and the owner-act verifier with its nonce table.
72
+ # It shares awid's did:key and canonical-JSON primitives and pgdbm, never the
73
+ # naapp runtime: importing it loads no web framework, settings or HTTP client
74
+ # (a subprocess test pins that). Installing awid-service brings that package's
75
+ # own dependencies, which the core server and every naapp already carry.
76
+ hooks = [
77
+ "awid-service>=0.5.30",
78
+ "pgdbm>=0.5.0",
79
+ "pynacl>=1.6.2",
80
+ ]
81
+
82
+ # Persistent pages need PostgreSQL and the frozen validator, not HTTP/auth deps.
83
+ page-store = [
84
+ "pgdbm>=0.5.0",
85
+ "jsonschema>=4.23",
86
+ ]
87
+
88
+ [project.urls]
89
+ Homepage = "https://github.com/awebai/aweb/tree/main/naapp-lib"
90
+ Repository = "https://github.com/awebai/aweb"
91
+ Issues = "https://github.com/awebai/aweb/issues"
92
+
93
+ [build-system]
94
+ requires = ["hatchling"]
95
+ build-backend = "hatchling.build"
96
+
97
+ [tool.hatch.build.targets.wheel]
98
+ packages = ["src/aweb_naapp"]
99
+
100
+ [tool.uv.sources]
101
+ awid-service = { path = "../awid", editable = true }
102
+ aweb-naapp-testing = { path = "testing", editable = true }
103
+
104
+ [dependency-groups]
105
+ dev = [
106
+ "aweb-naapp[runtime,pages]",
107
+ "aweb-naapp[egress]",
108
+ "cryptography>=44.0.0",
109
+ "aweb-naapp-testing",
110
+ "pytest>=9.0.0",
111
+ "pytest-asyncio>=1.2.0",
112
+ "httpx2>=2.9.1",
113
+ "ruff>=0.14.0",
114
+ "mypy>=1.18.0",
115
+ ]
116
+
117
+ [tool.pytest.ini_options]
118
+ pythonpath = ["src"]
119
+ testpaths = ["tests"]
120
+ asyncio_mode = "auto"
121
+ filterwarnings = ["error"]
122
+
123
+ [tool.ruff]
124
+ target-version = "py312"
125
+ line-length = 100
126
+
127
+ [tool.ruff.lint]
128
+ select = ["E", "F", "I", "B", "UP"]
129
+ ignore = ["E501"]
130
+
131
+ [tool.mypy]
132
+ python_version = "3.12"
133
+ mypy_path = "src"
134
+ ignore_missing_imports = true
135
+ warn_unused_configs = true
@@ -0,0 +1,2 @@
1
+ test-results/
2
+ playwright-report/