@poppykit/client 0.5.0 → 0.6.1

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 (51) hide show
  1. package/API.md +9 -1
  2. package/AUTH.md +97 -90
  3. package/CHANGELOG.md +20 -1
  4. package/CONFORMANCE.md +73 -0
  5. package/CONTRIBUTING.md +12 -11
  6. package/DELIVERY.md +19 -3
  7. package/INTEGRATIONS.md +35 -23
  8. package/OPERATIONS.md +120 -0
  9. package/PROVIDER.md +20 -12
  10. package/README.md +6 -17
  11. package/RELEASING.md +2 -0
  12. package/SECURITY.md +2 -0
  13. package/STORAGE.md +20 -7
  14. package/dist/adapter-contract.d.ts +6 -0
  15. package/dist/adapter-contract.js +30 -0
  16. package/dist/auth/challenge.d.ts +2 -0
  17. package/dist/auth/challenge.js +8 -8
  18. package/dist/auth/client-auth.d.ts +20 -7
  19. package/dist/auth/client-auth.js +386 -334
  20. package/dist/auth/sqlite-store.js +13 -4
  21. package/dist/auth/store.d.ts +6 -1
  22. package/dist/conversation.d.ts +6 -0
  23. package/dist/conversation.js +18 -14
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +2 -0
  26. package/dist/integrations.d.ts +11 -4
  27. package/dist/integrations.js +30 -38
  28. package/dist/network/discovery.d.ts +31 -13
  29. package/dist/network/discovery.js +103 -66
  30. package/dist/operations.d.ts +103 -0
  31. package/dist/operations.js +261 -0
  32. package/dist/postgres-runtime.js +12 -4
  33. package/dist/postgres.d.ts +11 -0
  34. package/dist/postgres.js +40 -3
  35. package/dist/protocol/operations.d.ts +51 -0
  36. package/dist/protocol/operations.js +98 -0
  37. package/dist/protocol/session.d.ts +28 -0
  38. package/dist/protocol/session.js +93 -0
  39. package/dist/protocol/sse.d.ts +1 -0
  40. package/dist/protocol/sse.js +1 -1
  41. package/dist/protocol/wire.d.ts +8 -4
  42. package/dist/protocol/wire.js +37 -15
  43. package/dist/provider-postgres.js +3 -3
  44. package/dist/provider-storage.js +4 -4
  45. package/dist/provider.d.ts +26 -3
  46. package/dist/provider.js +94 -13
  47. package/dist/sqlite.d.ts +12 -0
  48. package/dist/sqlite.js +42 -0
  49. package/examples/operations.mjs +25 -0
  50. package/examples/quickstart.mjs +1 -0
  51. package/package.json +5 -2
package/API.md CHANGED
@@ -29,7 +29,7 @@ The optional Postgres entry point ships its adapters, `createPostgresPool`,
29
29
  `postgresMigration`, `migratePostgres` and schema-version export; see [storage](STORAGE.md).
30
30
  TypeScript Postgres consumers also install `@types/pg@8.23.1` as a development dependency.
31
31
 
32
- Custom `AuthStore` implementations must honor `AuthSelection.history` as described
32
+ Custom `AuthStore` implementations must honor shared-grant/account aggregation and `AuthSelection.history` as described
33
33
  in [STORAGE.md](STORAGE.md). Provider callbacks now acknowledge identical consumed
34
34
  results without making `takeResult` reusable; see [PROVIDER.md](PROVIDER.md).
35
35
 
@@ -37,3 +37,11 @@ results without making `takeResult` reusable; see [PROVIDER.md](PROVIDER.md).
37
37
  `PostgresConnection` type are exported only by `@poppykit/client/provider/postgres`.
38
38
  They explicitly enlist provider SQL in a host-owned guarded transaction; the host
39
39
  retains commit/rollback, deadline and privilege responsibility.
40
+
41
+ ## Business Operations v2
42
+
43
+ See [OPERATIONS.md](OPERATIONS.md) for the complete consent/effect contract. Root exports include `BusinessOperation`, `OperationApproval`, `OperationRequest`, `operationsV2`, `supportsOperations`, `parseBusinessOperation`, `parseOperationApproval`, `parseApprovalHeader`, `approvalHeader`, `mcpApproval`, `approvalMetaKey`, `captureApiOperationRequest`, `operationExpiry`, `operationRequiredResponse` and `operationToolError`.
44
+
45
+ Client exports `createOperationClient`, `createOperationSender`, `OperationClientError`, the three approved-channel serializers, `BusinessClientContext`, `BusinessAttempt`, `BusinessAttemptStore`, `TrustedOperationConsent` and `OperationDelivery`. `ClientAuth.businessContext` supplies a verified binding. SQLite/PostgreSQL subpaths export `SqliteBusinessAttemptStore`/`PostgresBusinessAttemptStore`. The adapter-contract subpath exports `runBusinessAttemptAdapterContract`. Conversation options accept verified `operations:{client,company}` metadata.
46
+
47
+ For business-operation sends, pass the saved `BusinessClientContext` as the final `expectedContext` argument to `apiFetch`/`authorizedFetch`. Channel sender callbacks receive that context. A changed Company/client/User/account or signed-out Session fails before dispatch; target discovery does not authorize a replacement account. See [Operations](OPERATIONS.md).
package/AUTH.md CHANGED
@@ -1,92 +1,99 @@
1
1
  # Authentication lifecycle
2
2
 
3
- `createClientAuth` owns Direct, Device and Mediated sign-in, token renewal,
4
- replacement, revocation and abandoned-operation recovery. Call `recover()` on
5
- startup. Another live process is never interrupted; an ambiguous dispatched
6
- exchange is uncertain and must not be replayed.
7
-
8
- Each `start` creates a new Session Key; only the issuer-specific User ID is reused.
9
- Renewal uses that Session Key. `Connection.sessionId` remains a local identifier
10
- for the key's lifetime, not the company's internal Session ID. Token requests
11
- carry no `session_id`, and token responses need neither it nor `signed_in`.
12
- The grant determines sign-in state. Omitted signed-in scope inherits the request's
13
- scope context; an explicit response can narrow it but cannot expand it.
14
-
15
- After a refresh returns definite `invalid_grant`, the client diagnoses the Session
16
- with a signed-out assertion before discarding the Account Token. A live Session
17
- means the Account Token is unusable. An ended key is retired durably before a fresh
18
- key attempts replacement. Ambiguous refresh responses remain uncertain and are
19
- never resent. Sign-out still disconnects locally before remote cleanup.
20
-
21
- Discovery accepts only draft 0.3, checks the final well-known path (including
22
- cache revalidation), exact issuer and reciprocal `protected_resources`.
23
- `signInMethods(metadata)` derives Direct/Device from issuer grants and endpoints
24
- and uses `scopes_supported`; Mediated keeps `auth.mediated.scopes`. Company scope
25
- names cannot start with `poppy:`. Descriptions come from `auth.scopes` or a listed
26
- OpenAPI document. Prefer Direct when the user can receive its callback; Device is
27
- for appropriate device-flow situations. No sign-in method is user consent.
28
-
29
- Ask `authorization(connectionId, scopes)` for the current grant, missing scopes
30
- and advertised methods, with `scopeDescriptions` from `auth.scopes`. Choose a method explicitly in `beginSignIn`. Direct
31
- requires a registered HTTPS callback on the client domain, or an RFC 8252 HTTP
32
- loopback IP callback (`127.0.0.1` or `[::1]`), and an authenticated browser binding.
33
- Only a loopback registration's port may vary; callback completion and code exchange
34
- use the exact port selected at initiation. Metadata/network endpoint address
35
- protections do not change. The URL's `dpop_jkt` binds the code to this Session Key.
36
- Display its URL in the user's browser. Device returns a checked link/code and
37
- company identity; call `continueSignIn(operationId)` when its deadline is due.
38
- Do not automate user approval. Mediated calls the configured `TrustedInput`
39
- adapter for declared fields and correlated one-time codes. Only trusted UI code
40
- implements this adapter. Values are cleared after each submission and never
41
- persisted by the SDK or exposed in public results. Input adapters must not log
42
- values, keep copies, or return them through model tools.
43
-
44
- Direct callbacks accept OAuth error details and ignore unknown response fields.
45
- The registered origin/path/query, browser binding, state and exact issuer still
46
- have to match; duplicate recognized fields and a simultaneous code/error fail.
47
- Error descriptions and error URLs are never included in public auth outcomes.
48
-
49
- First-time partial grants complete with `grantedScopes` and `missingScopes`.
50
- Replacement preserves every old grant scope by default. An explicit
51
- `requiredExistingScopes` subset permits dropping other old scopes. A rejected
52
- replacement retains the previous grant and queues the new token for revocation;
53
- accepted replacements queue the previous token instead. Grants are never unioned.
54
- `force: true` represents an explicit user request to sign in again. Otherwise a
55
- sufficient current grant is reused. Run `retryCleanup` to retry durable retirement.
56
-
57
- Metadata is refreshed through the supplied resolver. A changed exact issuer or
58
- saved destination fails with `metadata_changed`; start a new connection after
59
- reviewing the new metadata. Saved work is never rerouted. Server Retry-After
60
- deadlines are persisted as absolute decimal milliseconds, including enormous
61
- values, and survive restarts without a local delay cap.
62
-
63
- `start(domain, { anonymous: true })` uses a fresh identity and DPoP key. Ending
64
- it deletes that private key and local identity; sign-in starts a new regular
65
- connection/session. Consume the returned connection ID and do not transfer old
66
- conversations. Regular issuer identity remains stable and separate.
67
-
68
- Provider adapters implement J1's `AssertionProvider` and `ProviderIdentity`;
69
- their authenticated identity supplies the issuer-specific UserID. Only purpose,
70
- issuer and operation binding are passed for assertions, never arbitrary bytes
71
- or claims. The operator must publish the signing key. For anonymous sessions a
72
- separate local `IdentitySigner` is required and its key must also be published in
73
- the client's JWKS. A provider-only configuration reports `anonymous_unavailable`;
74
- it never substitutes the provider's stable identity for an anonymous one.
75
-
76
- Provider `expiresAt` values are Unix seconds. Session/client assertions and browser
77
- joins accept a local freshness horizon of 60 seconds plus 30 seconds of clock skew;
78
- an assertion at or before the local current time is rejected. This freshness check
79
- does not verify the signed assertion's lifetime: the issuer verifies the signature,
80
- claims, replay and protocol maximum of five minutes between `iat` and `exp`.
81
- `provider_clock_skew` and `provider_assertion_expired` suggest checking automatic
82
- date/time and retrying. `provider_invalid_response`, `provider_unauthorized`,
83
- `provider_rate_limited` and `provider_network_error` distinguish malformed data,
84
- enrollment rejection, throttling and connectivity. Unknown failures remain
85
- `provider_unavailable`. Errors never include assertion or provider response text.
86
-
87
- Account Tokens, device codes, PKCE and DPoP keys belong in the configured
88
- SecretStore; Session Tokens stay in memory. SQLite and secret storage are separate
89
- commit boundaries. A host must sweep orphan staged secrets only after checking
90
- ownership and a grace period; crashes between stage and durable reference can
91
- leave such orphans. Cancellation/revocation generation checks defeat late results.
92
- Revocation stops future protected work and never automatically reconnects.
3
+ `createClientAuth` owns discovery, local Sessions, Direct/Device/Mediated OAuth,
4
+ refresh rotation, revocation and abandoned-operation recovery. Call `recover()`
5
+ on startup. A dispatched exchange with an ambiguous result is never replayed.
6
+
7
+ `start(domain)` creates a random Session ID and a pairwise User ID for the verified
8
+ Company identifier: its issuer, or `https://{organization.domain}` when issuerless.
9
+ The User ID is stable across Sessions. No token request is needed to start or use
10
+ a signed-out Session. Changing the Company identifier never transfers its identity,
11
+ grants or pending work. Domain discovery errors do not establish an issuerless Company.
12
+
13
+ Every protected request gets a fresh ES256 `Poppy-Session` JWT, bound to its final
14
+ method, URL without query/fragment, Company identifier and Session/User identity.
15
+ A private delegated key and root-signed `pap-delegation+jwt` vouch for the request;
16
+ the root key does not sign Session JWTs directly. Delegated keys are private to one
17
+ User ID at one Company. A token-bearing request also binds the token with `ath`.
18
+ OAuth endpoints receive client assertions and optional DPoP, without a Session JWT.
19
+
20
+ OAuth account grants are independent of Sessions. To reuse one explicitly:
21
+
22
+ ```ts
23
+ const other = await auth.start(domain, { accountFrom: signedInConnectionId });
24
+ ```
25
+
26
+ The source must belong to the same local user/client namespace and Company, be
27
+ regular rather than anonymous, and have an active grant. The new Session has its
28
+ own sid and signing keys. Refresh rotation is serialized on the shared grant;
29
+ ambiguous rotation stops every Session using it. A definite `invalid_grant` clears
30
+ the shared grant and continues those Sessions signed out.
31
+
32
+ Use `authorization(id, scopes)` for current granted/missing scopes and eligible
33
+ methods. Scopes may be documented outside `scopes_supported`; hints and descriptions
34
+ do not authorize anything. Direct requires a registered callback, high-entropy
35
+ browser binding, PKCE and exact callback `iss`. HTTPS callbacks stay on the client
36
+ metadata domain; RFC 8252 loopback IP callbacks permit only the port to vary.
37
+ The `dpop_jkt` is an OAuth proof-key binding, not the Poppy Session identifier.
38
+
39
+ Direct and Device are for the human's own sign-in and consent. Show the checked
40
+ Device link and code; keep the private device code out of model context. Never
41
+ complete the approval page for the User. Prefer Direct where viable, then Device.
42
+ Mediated v1 is transitional: the `mediated-sign-in` extension declares trusted
43
+ credential fields/scopes, and the issuer's token endpoint accepts
44
+ `https://personalagentprotocol.org/grant/mediated-sign-in` as a form grant.
45
+ Continuation sends `sign_in_id` and `one_time_code`, without original credentials
46
+ or scope. `TrustedInput` runs outside model tools and must not retain/log values.
47
+ The SDK clears supplied credential/code objects on exit and persists neither.
48
+
49
+ Successful fresh sign-in activates a new sid, preventing account mixing even after
50
+ sign-out. Failed replacement preserves the previous sid and grant. Partial grants
51
+ report granted/missing scopes. Replacement preserves old required scopes unless
52
+ `requiredExistingScopes` explicitly narrows that requirement; grants are not unioned.
53
+ An old refresh grant is retired when no other local Session still references it.
54
+ Accepting a newer grant does not expand a browser cookie's approved scopes.
55
+
56
+ Access requests use the response's Bearer or DPoP scheme. Missing `expires_in` uses
57
+ a conservative local 60-second lifetime. Missing rotated `refresh_token` retains
58
+ the existing one. Resource metadata selects narrower API/conversation/MCP tokens;
59
+ see [INTEGRATIONS.md](INTEGRATIONS.md). A repeated `invalid_session` restarts once
60
+ with a new sid and fresh signatures, then stops. Feedback replaces a definitely
61
+ ended sid but returns the failed POST without replay; an explicit later report
62
+ uses the new Session. Browser authorization can restart once before one code-only
63
+ browser submission. Session restart does not replay
64
+ an uncertain OAuth exchange or discard an otherwise valid account grant.
65
+
66
+ `signOut(id)` revokes every locally held refresh grant for that local User and
67
+ Company, clears their access credentials and continues regular Sessions signed out.
68
+ OAuth does not reveal a standard opaque account identifier, so this conservative
69
+ policy also signs out other accounts for that User at the same Company. Cleanup
70
+ references survive remote/vault failures; call `retryCleanup(id)`. A pending or
71
+ uncertain outcome is never reported as complete. Re-sign-in requires a User request.
72
+
73
+ `start(domain, { anonymous: true })` gets a fresh User ID, sid and private keys.
74
+ It never carries an account token. Sign-in ends it and starts a regular connection;
75
+ use the returned connection ID. Ending anonymous browsing deletes its local private
76
+ identity/key material, with durable cleanup references if deletion fails.
77
+
78
+ `AssertionProvider.assert` supplies constrained client authentication;
79
+ `delegate({operationId,jwk})` supplies a delegation for a locally generated public
80
+ key. `ProviderIdentity` supplies the authenticated Company-specific identity.
81
+ The optional bundled provider uses separate enrolled client identities; see
82
+ [PROVIDER.md](PROVIDER.md). A provider-only configuration cannot substitute its
83
+ regular identity for anonymous browsing. ES256 is supported directly; a host using
84
+ blind PS384 root signatures supplies its own assertion/delegation adapter and
85
+ protects cross-user isolation. Blind signing is not implemented by the bundled provider.
86
+
87
+ Private keys, refresh tokens, device codes and PKCE belong in the host SecretStore;
88
+ access tokens remain in process memory. The store and vault are separate commit
89
+ boundaries: sweep orphan staged secrets only after ownership checks and a grace
90
+ period. Preserve uncertain requests and cleanup references. See [STORAGE.md](STORAGE.md)
91
+ for record upgrades and [DELIVERY.md](DELIVERY.md) for operator-signature integration.
92
+
93
+ Delegations use coarse half-hour issuance buckets with a one-hour signed lifetime,
94
+ leaving 30–60 minutes at creation. Host providers may return a different valid
95
+ lifetime within the client's one-hour freshness bound. See [CONFORMANCE.md](CONFORMANCE.md).
96
+
97
+ Authentication lifecycle OperationRecord/operationId values never approve business operations. OAuth access consent, provider enrollment and mediated credential collection cannot supply business consent. [Operations v2](OPERATIONS.md) binds each actual user answer to the exact revision and request, and rechecks current Session/account authority for execution and protected replay.
98
+
99
+ For business-operation sends, pass the saved `BusinessClientContext` as the final `expectedContext` argument to `apiFetch`/`authorizedFetch`. Channel sender callbacks receive that context. A changed Company/client/User/account or signed-out Session fails before dispatch; target discovery does not authorize a replacement account. See [Operations](OPERATIONS.md).
package/CHANGELOG.md CHANGED
@@ -1,8 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ Local migration block2 adds Operations v2 codecs, negotiated API/MCP/conversation mechanics and durable trusted-consent attempt storage/recovery. See [OPERATIONS.md](OPERATIONS.md). This is a local SDK checkpoint, not full CLI integration or package publication.
4
+
5
+ ## 0.6.1 — 2026-11-01
6
+
7
+ Handle PostgreSQL socket loss while an SDK transaction or migration owns a
8
+ checked-out connection. Invalidate failed transaction handles, discard broken
9
+ connections, and preserve the original migration error when rollback also fails.
10
+ A failed COMMIT still requires durable-state reconciliation before retry.
11
+
12
+ ## 0.6.0 — current-spec core migration
13
+
14
+ Separate delegated resource Sessions from OAuth account grants; discover issuers and
15
+ service resources independently; support issuerless Companies, Bearer/DPoP tokens,
16
+ current mediated grants, code-only browser joining, current errors and feedback.
17
+ Client auth records upgrade to v3; reference issuer records upgrade to v5 without
18
+ resetting credentials or pending/recovery records. Stop old writers before opening
19
+ upgraded stores. Retired public APIs are removed; see [AUTH.md](AUTH.md) and
20
+ [STORAGE.md](STORAGE.md). Breaking local migration candidate; publication requires the recorded release gates.
21
+
3
22
  ## 0.5.0 — 2026-10-28
4
23
 
5
- Target pinned Personal Agent Protocol draft 0.3. Break discovery, Session Key/token, author, browser-controller and feedback contracts; retain explicit storage subpaths. Upgrade normalized auth state 1→2; stop old writers and reset incompatible conversation/outbox state before cutover.
24
+ Historical snapshot of Personal Agent Protocol draft 0.3; the current live contract supersedes this entry. Break discovery, Session Key/token, author, browser-controller and feedback contracts; retain explicit storage subpaths. Upgrade normalized auth state 1→2; stop old writers and reset incompatible conversation/outbox state before cutover.
6
25
 
7
26
  Local migration candidate only. Publication and deployment require separate approval.
8
27
 
package/CONFORMANCE.md ADDED
@@ -0,0 +1,73 @@
1
+ # Current protocol contract
2
+
3
+ The normative source is the [live draft 0.3 specification](https://personalagentprotocol.org/docs/0.3/spec)
4
+ and [Mediated Sign-In v1](https://personalagentprotocol.org/docs/0.3/extensions/mediated-sign-in).
5
+ The numbered URL is not a content pin. On 2026-10-31 the six migration sources were
6
+ re-fetched and matched their initial snapshots byte for byte. SHA-256 identities:
7
+
8
+ - Core: 0c8586c0477f523abe39b47e5a20232fc8128c24bf17bb78fff69133f97f9bb9
9
+ - Mediated v1: 68074a6178bf4c78f09b2a88313a4067fa9e4143a338f205a63d2d884ca09f94
10
+ - Guides: fe10657eca0b464c19cc198f3876d30f5055d8a5cb423cd147affe805206ecdd
11
+
12
+ The workspace requirement matrix and exact commands, tree hashes and focused test
13
+ receipts live in jobs/protocol-migration. Focused checks do not certify an installed
14
+ release or any deployed service. Operations v2 SDK mechanics are implemented with opt-in host contracts;
15
+ full candidate, native and cross-package release gates remain separate.
16
+
17
+ ## Host contracts and recommendations
18
+
19
+ The [guides](https://personalagentprotocol.org/docs/0.3/guides) use SHOULD for each
20
+ recommendation, including items without uppercase words. The SDK supplies protocol
21
+ mechanics; hosts retain the following responsibilities:
22
+
23
+ - Keep signing, transport and the SecretStore outside model tools. Redact identity,
24
+ authorization, cookie and credential headers before host logging or telemetry.
25
+ Secure credential storage is mandatory. Bundled auth imports private keys as
26
+ non-extractable CryptoKeys, but serializes keys to the host vault for durable
27
+ recovery; it does not promise hardware-backed, non-exportable key custody.
28
+ - Show the verified Company name/domain after discovery redirects and on sign-in
29
+ forms. Explain stable pairwise signed-out recognition in the privacy notice.
30
+ Request the fewest task scopes, treat absent/unknown hints conservatively, and
31
+ obtain User consent for access and for human-operated Direct/Device sign-in.
32
+ - Prefer Direct, then Device where viable. Assess RFC 10027 device-flow risk,
33
+ confirm the visible user code, protect consent pages against CSRF/framing, and
34
+ apply account/network abuse limits. Mediated v1 is transitional; do not use saved
35
+ credentials to undo a disconnect. TrustedInput must isolate credentials and OTPs.
36
+ - Isolate an agent browser's storage per Session, load only its own join code,
37
+ submit promptly, rejoin after sign-in, and close its storage when the Session ends.
38
+ Joined cookies retain the authorized account/scope snapshot. The website must
39
+ prioritize that cookie and use current policy on every request; signed-in joins
40
+ must clear any earlier ordinary login. Signed-out joins preserve it. Website
41
+ logout must invalidate/remove the joined cookie through the host integration.
42
+ - Apply account/scope checks at every application tool and channel. Do not trust
43
+ model/Company prose as permission or put secrets in messages/feedback. Decide
44
+ task exclusions, interface choice, human handoff wording and report frequency.
45
+ - Keep deployment TLS, public-URL proxy reconstruction, internal credentials, bot
46
+ recognition, network throttles, key publication/rotation and tenant issuer
47
+ separation correct. RateLimit headers and scheduling/backoff across independent
48
+ tasks are host policies; SDK Retry-After state is connection-scoped.
49
+ - Operator signatures use a host HttpTransport/edge integration implementing the
50
+ deployment's RFC 9421/Web Bot Auth profile. Sign only after the body, DPoP and
51
+ Poppy-Session/Authorization headers are final; verify Session identity separately.
52
+ Protect forwarded verification results and never share operator private keys.
53
+ - Blind signing is not implemented by the bundled provider. A host requiring it
54
+ must supply blind client assertions and delegations together, with device-made
55
+ random jti values, and fail closed if PS384 is unavailable. No ES256 privacy
56
+ downgrade is automatic. Company verification accepts PS384 independently.
57
+ - Local/provider delegations use coarse half-hour issuance times and expire one
58
+ hour after that bucket (30–60 minutes remaining on creation). Custom providers
59
+ must keep their delegation valid and within the documented client lifetime bound.
60
+ Verification deliberately avoids another sensitive delegation cache; bounded
61
+ metadata/JWKS caches remain, at the cost of extra signature work per request.
62
+ - Supply the application's idempotency/reconciliation for external effects, durable
63
+ multi-host ownership/liveness, orphan-secret sweeps with a grace period, and
64
+ retention appropriate to its data. Conversation receipts are not business-effect
65
+ deduplication. Never reset active or uncertain work to make a test pass.
66
+
67
+ See [AUTH.md](AUTH.md), [STORAGE.md](STORAGE.md) and [INTEGRATIONS.md](INTEGRATIONS.md)
68
+ for the concrete callback/store contracts. A host integration must validate these
69
+ boundaries in its real browser, issuer, vault, gateway and deployment environment.
70
+
71
+ Operations source re-fetch: 2026-10-31, HTML SHA256 e69ade54433ece3fb2bd1ff6c441fecd11acc749eb6bac8b33939420d1f2b9eb, unchanged from block1. Core and guide hashes are unchanged as well. [Business Operations](OPERATIONS.md) defines trusted UI/business semantics, durable adapters and external idempotency/reconciliation obligations. Focused SQLite/PostgreSQL and process-crash evidence belongs to block2; full stable-candidate/package/platform gates remain block3. No deployed host or arbitrary network exactly-once claim is made.
72
+
73
+ Source revalidation on 2026-11-01 found all six core/current/changelog/Operations/mediated/guides pages byte-identical to the 2026-10-31 snapshots. A numbered draft URL remains mutable. Final candidate verification and external platform limits are recorded in the workspace migration STATUS; package publication is established only by its release receipt.
package/CONTRIBUTING.md CHANGED
@@ -1,32 +1,33 @@
1
1
  # Contributing
2
2
 
3
- Follow the pinned draft 0.3 [specification](https://personalagentprotocol.org/docs/0.3/spec)
3
+ Follow the live draft 0.3 [specification](https://personalagentprotocol.org/docs/0.3/spec)
4
4
  and [guides](https://personalagentprotocol.org/docs/0.3/guides). Tests/snapshots are
5
- supporting evidence. This unreleased checkout has completed only the discovery
6
- stage of migration; auth, wire/storage and integrations still need their stages.
7
- Demo/business workflows and individual extensions are excluded.
5
+ supporting evidence. Core protocol, Mediated Sign-In v1 and [Operations v2](OPERATIONS.md) are in scope.
6
+ Operations capability publication requires complete host consent/effect integration.
7
+ Application business workflows remain host responsibilities. Source fingerprints and
8
+ recommendation boundaries are in [CONFORMANCE.md](CONFORMANCE.md).
8
9
  Code is [MIT](LICENSE); confirm rights for third-party contributions.
9
10
 
10
11
  Use Node 24.21.0, TypeScript 5.9.3 and @types/node 24.10.0.
11
12
  Install the packageManager-pinned pnpm and run pnpm install --frozen-lockfile --ignore-scripts.
12
13
 
13
14
  ```sh
14
- npm run typecheck
15
- npm run build
16
- npm run typecheck:tests
17
- npm test
18
- npm run check:docs
15
+ pnpm run typecheck
16
+ pnpm run build
17
+ pnpm run typecheck:tests
18
+ pnpm test
19
+ pnpm run check:docs
19
20
  ```
20
21
 
21
22
  First-party tests/workers/verification helpers are canonical `.mts`. The existing
22
23
  compiler checks the whole strict program before emitting ignored adjacent `.mjs`.
23
24
  Failed builds remove stale outputs; no loader/type stripping substitutes for checking.
24
- Run `npm run build:tests` before `node scripts/run-checks.mjs GROUP` (unit, contracts,
25
+ Run `pnpm run build:tests` before `node scripts/run-checks.mjs GROUP` (unit, contracts,
25
26
  integration, validation). Groups are exhaustive/disjoint. LF source bytes are enforced
26
27
  by Git attributes. Generated test code is outside the npm allowlist; production tests
27
28
  import built/installed JavaScript. Release/build tools and examples may remain JS.
28
29
 
29
- `npm run coverage` builds production and checked tests, then writes native Node
30
+ `pnpm run coverage` builds production and checked tests, then writes native Node
30
31
  line/branch/function reports under `coverage/` (summary.txt, lcov.info, report.json,
31
32
  raw/). Metrics cover loaded first-party dist JS: no production source maps or TS
32
33
  remapping; node_modules excluded, unloaded modules listed. Raw subprocess reports
package/DELIVERY.md CHANGED
@@ -100,8 +100,24 @@ unsupported. Human text must remain the person's exact words. Company authors
100
100
  may include `id`, `name` and an HTTPS `image`. Authorization events now contain
101
101
  `error: 'insufficient_scope'` and nonempty required `scope`.
102
102
 
103
- No backward compatibility is provided. Stop writers and explicitly reset old
104
- history, outboxes, checkpoints and caller retry handles before cutover, preserving
105
- configuration. Old pending `sender` requests fail `legacy_message_uncertain`
103
+ Stop incompatible writers before cutover. Preserve old history, outboxes,
104
+ checkpoints and retry handles for explicit reconciliation; do not silently reset them. Old pending `sender` requests fail `legacy_message_uncertain`
106
105
  without dispatch or mutation. Reconcile uncertain delivery before clearing it;
107
106
  never rebuild the body or allocate a replacement ID to hide uncertainty.
107
+
108
+
109
+ ## Operator signatures
110
+
111
+ A host should wrap the supplied HttpTransport with an RFC 9421/Web Bot Auth signer.
112
+ The auth lifecycle passes the finalized Request to both fetch and stream, after
113
+ setting its fresh Poppy-Session and optional Authorization/DPoP headers. Sign at
114
+ that boundary on every call, including retries, and forward the same method, target,
115
+ body and signed headers without redirects. Cover poppy-session and authorization
116
+ when present, in addition to the Web Bot Auth required components. Apply identical
117
+ signing behavior to streaming calls. Never log the Request or signing material.
118
+ The host supplies operator keys and the standards-compliant signer; the SDK does
119
+ not invent a second operator-key registry or treat its signature as Session verification.
120
+ Operator keys and delegated Session keys have separate roles. Companies still
121
+ verify the Session independently, and may apply operator acceptance policy.
122
+
123
+ Business-operation retries use the operation ID and approved revision, independently of message/work IDs. A conversation acceptance is not proof the action completed. [Operations v2](OPERATIONS.md) preserves uncertain claims/attempts through timeout, process death, expiry and restart. A different operation returned on retry does not establish the earlier outcome.
package/INTEGRATIONS.md CHANGED
@@ -19,26 +19,31 @@ const result = await fetchForClient(new Request('https://company.example/api/rec
19
19
 
20
20
  OpenAPI `apiUrl` is the description URL from verified company discovery. The third
21
21
  argument is the actual destination base; its host must independently serve verified
22
- company discovery naming the same issuer. This conservative policy requires API
22
+ company discovery naming the same verified Company identifier. This conservative policy requires API
23
23
  hosts to publish discovery; a document's `servers` field alone grants no authority.
24
24
  Final requests must stay within the verified origin and slash-delimited base path.
25
25
  The proof binds the actual final method and URL. Destination changes require a new
26
26
  explicit connection; saved work never moves silently.
27
27
 
28
- For MCP, use `auth.apiTarget(connectionId, mcpUrl)` and the same fetch closure in an
29
- existing Streamable HTTP MCP client (version 2025-06-18 or later). The SDK does not
30
- encode JSON-RPC or manage MCP sessions. The exact discovery URL is the resource and
31
- endpoint; extra paths or queries are refused. Query-bearing discovery endpoints are
32
- not supported. Protected-resource metadata must name that exact resource and the
33
- verified company issuer. ES256 DPoP metadata selects DPoP; absence selects Bearer.
34
- A Bearer-only MCP resource cannot also authorize a conversation. Resource-specific
35
- credential slots use the shared nonce, cooldown and revocation
36
- lifecycle. Bearer-only MCP credentials come only from Token Exchange of a current
37
- DPoP Session Token, preserving its Session and limiting scopes to that subject. The
38
- issuer must advertise the exchange grant; exchange does not rotate the Account Token.
39
- DPoP-capable MCP keeps the normal token grants. The wire `token_type` must match verified metadata; there is no fallback
40
- from DPoP to Bearer. Redirects never forward tokens or proofs. Auth 401 rejection may
41
- cause one fresh-token retry; unknown results are not automatically retried.
28
+ Service authorization comes from RFC 9728 protected-resource metadata, fetched
29
+ without credentials before the first signed-in API/conversation request and cached
30
+ through the resolver. A real 404 permits the default Company token; network errors
31
+ and malformed metadata fail closed. A service resource other than the Company
32
+ domain selects a token for that exact resource via normal sign-in/refresh. Discovery
33
+ resource fields do not grant authority. An invalid_token challenge with
34
+ resource_metadata causes one bounded revalidation/refresh retry.
35
+
36
+ For MCP, use auth.apiTarget(connectionId, mcpUrl) and the same fetch closure in an
37
+ existing MCP HTTP client. The exact discovery URL is the resource and endpoint,
38
+ including any query; changed queries or child paths are refused. Its metadata must
39
+ name that resource and the Company's issuer when sign-in exists. ES256 DPoP
40
+ advertisement selects DPoP; absence selects Bearer. Account credentials use normal
41
+ OAuth grants, without token exchange. Requests use the token response's scheme.
42
+
43
+ The SDK does not encode JSON-RPC or manage MCP initialization/Mcp-Session-Id.
44
+ Poppy Session continuity is independent of earlier MCP transport lifecycles and
45
+ MCP 2026-07-28 per-request metadata. Signed-out issuerless APIs/MCP still get Session
46
+ JWTs. Redirects never forward tokens or proofs; unknown results are not retried.
42
47
 
43
48
  ## Agent-controlled browser
44
49
 
@@ -65,35 +70,36 @@ await auth.joinBrowser(connectionId, 'https://company.example/account', {
65
70
  `controlledBrowser` is the host's existing controller, not an SDK driver. This hook
66
71
  is trusted code. Its network layer must reject loopback/private/link-local actual
67
72
  connected addresses unless narrowly configured, validate TLS and enforce redirect
68
- interception. Never replay the code or proof on 307/308 or send it in a URL. The supplied
73
+ interception. Never replay the code on 307/308 or send it in a URL. The supplied
69
74
  validator permits only 303 to an HTTPS company page. After that redirect, the SDK
70
75
  asks the controller to navigate to the validated `returnTo`. HttpTransport cannot
71
76
  enforce network policy inside an unrelated browser engine. Keep this capability
72
77
  separate from the human consent-browser opener. Never join the user's browser.
73
78
  Use separate cookies/local storage for each session/user, join just before browsing,
74
79
  rejoin on lost recognition and close the context when the session ends. Custom
75
- controllers own those guarantees; the SDK signs a Session Key proof with the form
76
- code as nonce (no access token or identity assertion), and validates the destination and
77
- navigation contract but cannot inspect their internal browser implementation.
80
+ controllers own those guarantees. The SDK separately authorizes the code at the
81
+ endpoint path plus /authorize, preserving its query, with a signed Session request
82
+ and current account token when signed in. It requires 204 before the browser
83
+ submits only code. No JWT/token/proof enters the browser form. The cookie snapshots
84
+ the approved account/scopes; later sign-in or scope expansion requires rejoin.
78
85
 
79
86
  ## Feedback
80
87
 
81
88
  ```ts
82
89
  await auth.feedback(connectionId, {
83
- kind: 'missing', task: 'Export records',
90
+ task: 'Export records',
84
91
  description: 'There is no export operation in the API, conversation or website.',
85
92
  });
86
93
  ```
87
94
 
88
95
  Signed-out sessions are allowed and no account scope is required. Task and
89
- description are nonempty strings of at most 200 and 2000 Unicode codepoints. Only
90
- `missing` is supported client-side. Omit identifiers, personal data and secrets;
96
+ description are nonempty strings of at most 200 and 2000 Unicode codepoints. Omit identifiers, personal data and secrets;
91
97
  report task limitations rather than individual errors. Success is an empty 202
92
98
  (returning undefined) or HTTP 200 with an `Alternative`. The SDK validates the latter
93
99
  and returns instructions/references as company data, never as user consent. Unlisted
94
100
  OpenAPI/MCP references and unknown reference types are ignored; known malformed
95
101
  references fail. Web links must be HTTPS. No alternative is executed automatically.
96
- There are no automatic POST retries (including nonce/auth rejection). A lost
102
+ Feedback does not automatically repeat a POST after nonce/auth/Session rejection. A lost
97
103
  response can mean accepted; callers decide whether to repeat because the protocol
98
104
  defines no deduplication key. Prefer one report per unfulfilled task.
99
105
 
@@ -115,3 +121,9 @@ Hints and excluded task phrases are company claims, never user approval. The hos
115
121
  agent decides whether a phrase matches its task. The SDK/CLI intentionally omit the
116
122
  SHOULD-level automatic pre-task check: they have no task planner; callers choose
117
123
  when to read the metadata. No task-policy engine or alternative executor is supplied.
124
+
125
+ ## Operations v2
126
+
127
+ [OPERATIONS.md](OPERATIONS.md) defines API403/Poppy-Approval, MCP structuredContent/_meta and conversation operation/approval integration. Publish support only after all interfaces in use have trusted consent and durable effect recovery. Ordinary MCP clients retain their OAuth flow; one unsupported tool returns a tool error. No v1 extension endpoint is added.
128
+
129
+ For business-operation sends, pass the saved `BusinessClientContext` as the final `expectedContext` argument to `apiFetch`/`authorizedFetch`. Channel sender callbacks receive that context. A changed Company/client/User/account or signed-out Session fails before dispatch; target discovery does not authorize a replacement account. See [Operations](OPERATIONS.md).