aweb-naapp
The reusable Native Agentic App (naapp) docs surface. Every naapp built with it serves the same standard surface — so the standard is shared code, not a doc copied by hand.
An app supplies its canonical aweb-app.json manifest, a SiteConfig (brand,
nav, footer), and its own landing body, and gets:
- the aweb design system (Paper/Clay), vendored and sha-pinned, served at
/css/aweb.css; - the shared chrome — head, header, footer, theme toggle, and copy button;
- manifest-driven llms.txt blocks — the operations list and the team-certificate authentication section;
- the
/referencepage — every operation in parallel as the canonicalaw <verb>command and the raw HTTP wire format, with the honest team-cert v2 envelope linked to the conformance vector.
import aweb_naapp as naapp
site = naapp.SiteConfig(
origin="https://library.aweb.ai",
brand="library",
title="library — agent profiles for AWID teams",
description="...",
nav_links=(naapp.NavLink("Reference", "/reference"), ...),
footer_blurb="...",
footer_columns=(...),
footer_bottom="library is a Native Agentic App on the aweb.ai hub. AWID is the identity authority.",
)
reference_html = naapp.render_reference(manifest, site, verb="library")
landing_html = naapp.page(site, body_html, include_copy_llms=True)
css_bytes = naapp.aweb_css()
The docs surface is pure standard library. Request-time runtime code (the
team-auth verifier, approvals, passkeys, and team-server discovery from the
team's AWID aweb.team-server/1 record) lives under aweb_naapp.runtime and
needs the runtime extra: aweb-naapp[runtime]. Two parts the core server
shares import without it: the durable outbox, aweb_naapp.outbox
(aweb-naapp[outbox]), and the signing-key reader, aweb_naapp.signing_key
(aweb-naapp[signing-key]).
The egress guard, aweb_naapp.egress, is for naapps that call an external
service with their own credential. It needs only the egress extra,
aweb-naapp[egress], and none of the runtime. It canonicalizes and matches
outbound URLs under the contracts in docs/vectors/egress-url-v2.json and
docs/vectors/egress-address-v1.json, resolves names itself and refuses any
denied address, and sends a request only to the destination it matches,
through one pinned, verified connection. Redirects come back as responses, no proxy is read from
the environment, and a failure is classed as not sent or of unknown outcome.
App keys
An app names its signing keys by kid, as key files:
<PREFIX>APP_KEY_FILES='{"<kid>": "/run/secrets/<file>"}'. A key file holds the
32-byte Ed25519 seed as 64 hex characters, with one trailing newline allowed. It
must be a regular file of at most 1 KiB, and must not be group- or world-writable. The manifest
declares which kid signs which purpose, and the app does not start unless the
configured key is the declared one. An app with <PREFIX>APP_EVENTS_ORIGIN set
(https, or http on loopback) must hold its declared app-event key. That key may instead be given as
<PREFIX>APP_EMIT_KEY_SEED_HEX with <PREFIX>APP_EMIT_KID, a form that signs
app events only.
The egress guard, aweb_naapp.egress, is for naapps that call an external
service with their own credential. It needs only the egress extra,
aweb-naapp[egress], and none of the runtime. It canonicalizes and matches
outbound URLs under the contracts in docs/vectors/egress-url-v2.json and
docs/vectors/egress-address-v1.json, resolves names itself and refuses any
denied address, and sends a request only to the destination it matches,
through one pinned, verified connection. Redirects come back as responses, no proxy is read from
the environment, and a failure is classed as not sent or of unknown outcome.
Health
/live reports the app's id and build commit. /health and /ready also run
the app's dependency checks and report each by name: ready is 503 when any
check fails, and health always answers 200. The runtime checks its database
and reports the age of its oldest undelivered outbox event
(NaappRuntime.health_checks()). An app adds its own as async callables that
return a CheckResult. A check that raises, or does not answer within 5 s,
fails, and its error is logged, never served. /ready serves only whether each
check passed; /health adds each check's detail, whose values are numbers,
booleans or null, never text. The routes are unauthenticated,
so the checks run at most once every 10 s: concurrent requests share the run in
flight, and later ones get its result until it expires.
Agent and human origins
An app with pages for people in a browser serves them on their own origin.
aweb_naapp.runtime.origins.split_origins(agent_app, human_app, agent_origin=..., human_origin=...) is one ASGI app that dispatches on Host:
- the agent origin serves the runtime's app, with no cookies in either direction, so its only authority is the team-auth signature;
- the human origin refuses any request carrying
Authorization,X-AWEB-*orX-AWID-*(403 team_auth_refused); - any other host is
421 unknown_origin.
A route exists on one origin because it is declared in one of the two apps. The lifespan is the agent app's.
Approvals and passkeys
aweb_naapp.runtime.approvals is the single-use human approval primitive that
apps share. An agent proposes an exact request; the owner-designated human
approves or rejects it; a trusted dispatcher then admits exactly one dispatch of
that exact request.
- Frozen request.
request_sha256is SHA-256 overaweb.approval-request/1\nplus the canonical JSON of the request and its policy reference and version. Changing any covered value needs a new approval.parse_request_jsonrefuses duplicate keys, floats and non-finite numbers. - Who decides. The app's
ApproverDirectorynames the owner-designated approver for the policy; the requester never chooses the approver, the contact or the delivery. The requester sees onlyRequesterView(request_id, state, delivery status, outcome): never a link, token or capability. - Presentation. A link presentation sends a one-use link to the approver's
registered contact through the app's
ApprovalDelivery; only its SHA-256 verifier is stored. The link only locates the request. Opening it consumes nothing; a successful decision uses it. Under a passkey-minimum policy the request is shown only after aviewassertion, which consumes nothing; otherwise, or when the policy setslink_reveals_request, link possession alone reveals the request. A task presentation needs the app'sTaskAnswerVerifierto verify the member-signed answer and its receipt, and the authenticator proof is bound to that task answer; the approval stays pending until both verify. - Assurance. Each
ActionPolicysets a minimum ofmember_sessionorpasskey. AnAuthenticatoradapter derives the evidence from a verified proof; a caller cannot claim an assurance by naming it. The passkey adapter is below;member_sessionis an adapter seam for apps that have member sessions. Every proof is bound bychallenge_bindingto one approval, purpose (approve, reject or view) and task answer. - Current approver. An approval is only as good as its approver's current
designation: challenge, decide and admit refuse with
approver_retiredonce theApproverDirectoryno longer names the approver. - Admission.
admit()is a compare-and-set under a row lock and a lock per request digest. A retry under the same operation ID retrieves the admission and is not a new dispatch; afternot_sentit allows a transport retry while the approval is unexpired and unrevoked. The outcome lives on the consumption:dispatching → completed | not_sent | outcome_unknown, and the owner reconcilesoutcome_unknown. While a dispatch of the same request digest is dispatching or unknown, no other approval of it is admitted. No outcome rearms an approval. - Transactions.
decide,admit,record_outcome,reconcile,revokeandrevoke_manytake an optional pgdbm transaction from the same database, so the change commits with the app's own writes. Audit events go to the app'sAuditSinkinside the transaction of the change they record. - Storage.
ApprovalStoreis the interface;PgApprovalStore(db)keeps the records in the app's own pgdbm schema, andmigrate()applies its migrations (moduleaweb_naapp_approvals).
aweb_naapp.runtime.passkeys verifies app-local WebAuthn passkeys with
py_webauthn, maintained by Duo Labs
since 2017. It does all of the WebAuthn cryptography and
parsing: attestation formats, COSE keys, signature and counter checks. Its
server-only API takes the expected challenge, relying party ID, origin and
user-verification requirement as explicit arguments, which this module binds to
the approval, decision, policy and the app's own origin.
- The relying party is the app's own origin (
RelyingParty). verify_registration(credential, challenge=..., rp=...)andverify_assertion(proof, challenge=..., rp=..., credential_id=..., public_key=..., sign_count=...)are the stateless ceremony checksPasskeysuses, for any caller with its own challenges and credentials. They always require user verification and raiseWebAuthnRefusedon any failure.verify_assertiontakes the stored credential the caller chose for the expected subject and refuses a proof naming any other credential, since the proof's credential ID is not signed. The caller makes each challenge single-use, stores the credential and advances its counter with a compare-and-set onnew_sign_count.designate(tx, scope=..., contact=..., evidence=...)runs in the caller's transaction. The evidence is aVerifiedOwnerAct, which the owner-act verifier (aweb_naapp.hooks.owner_act_nonces) consumed in that same transaction:app-role.designatefor a scope's first approver,app-credential.recoverto replace one. The act names the scope, commits to the contact by hash and takes effect once. For a scope the app does not count as an owner scope (Passkeys(owner_scopes=...), every scope by default), the evidence may instead be anOwnerApprovalfromowner_approval(): an owner's decided approval, admitted and completed through B2 in the same transaction. It designates only a scope's first approver. Whether the approved request asks for that designation is the app's check.- Nothing is sent inside the transaction. After the commit the caller sends the
enrolment link and any recovery notices with
deliver(designation). If that never happens,reissue_enrolment(tx, scope=...)revokes the unused link and issues a new one to the same contact, while the approver has not enrolled. - The enrolment link goes only to the designated contact. Whoever first completes enrolment through the link registers the passkey: this is trust on first use, and the audit event records it as such. The link is as strong as the contact channel it travels through.
- Recovery from a lockout retires the previous approver at once, with its
passkeys, unused enrolment links and live approvals (through
ApprovalRevocation, in the same transaction). The new approver takes effect after a cooling-off period (LockoutPolicy, at least 72 h), during which the scope has no approver, and every contact previously recorded for the scope is notified.recovery_pending_untiltells the app to freeze its own widening changes meanwhile. retire(tx, scope=..., evidence=...)ends a scope's designation on anapp-role.revokeact consumed in the same transaction, for instance when the owner role behind the scope is revoked. The act'ssubject_refmust name the approver retire finds current, so a recovery committed after the act was signed is never retired by it. The current approver, or a recovery's pending one, is retired with its passkeys, unused enrolment links and live approvals. The scope then has no approver: its next designation is a first one and enrols a new passkey, and the retired passkeys stay dead.- A contact change needs the approver's own passkey, takes effect after a delay (at least 24 h) and is notified to the old and the new contact.
- These durations are floors. App or owner policy may lengthen them, and
deploy configuration may raise the floor itself (
lockout_floor); nothing can go below 72 h and 24 h. - Assertions are bound to one approval, purpose, task answer, request digest
and policy version through
challenge_binding. They need user verification, are single-use, and the authenticator counter must advance. - Designation is the owner's attestation of one natural person. The primitive
cannot detect sharing: a passkey or contact that several people share makes
every approval attributable only to the group. Every
Authenticatormust likewise refuse non-individual principals; amember_sessionadapter needs a per-member principal from the identity layer or a custody attestation. login_options(scope)/verify_login(scope, proof)log in a scope's in-effect approver under the same rules, returningPasskeyLogin(subject, contact, evidence_ref). What a login opens (a console or reader session) is the caller's. Roles are named scopes, such assecrets.owner, each designated through a team owner act.- A passkey proves that an app-local human holds the enrolled authenticator. It is not AWID membership, team identity or key custody.
Metadata
Release files for aweb-naapp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aweb_naapp-0.1.0.tar.gz | 535.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aweb_naapp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 854.1 kB
Release files / aweb_naapp-0.1.0.tar.gz
| Download URL | aweb_naapp-0.1.0.tar.gz |
|---|---|
| Size | 535.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e30c05f0edb2ceb5064608a998f13f27b332c33461d571ee6082f66a1b3ed309
|
|
BLAKE2b-256 checksum How to use checksums |
dcb4748833852a69840faef05f1bb1ceec9069e620b4d0c150ecf07eb2bb81af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / aweb_naapp-0.1.0-py3-none-any.whl
| Download URL | aweb_naapp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 318.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
787022589f333aedaeea8e236a458fa6da6cd9d163eef0ae42e30bd4c090f93c
|
|
BLAKE2b-256 checksum How to use checksums |
c4bf4696698dfc4be54b92f277fb320940fdf57e4dc7d92e08af033eb8f70208
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for aweb-naapp 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.