@poppykit/server 0.0.0-stage → 0.1.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 +21 -0
  2. package/AUTH.md +60 -0
  3. package/CHANGELOG.md +18 -0
  4. package/CONTRIBUTING.md +38 -0
  5. package/DELIVERY.md +77 -0
  6. package/INTEGRATIONS.md +84 -0
  7. package/LICENSE +21 -0
  8. package/README.md +67 -2
  9. package/RELEASING.md +91 -0
  10. package/SECURITY.md +13 -0
  11. package/STORAGE.md +112 -0
  12. package/dist/adapter-contract.d.ts +12 -0
  13. package/dist/adapter-contract.js +140 -0
  14. package/dist/auth/authenticator.d.ts +6 -0
  15. package/dist/auth/authenticator.js +51 -0
  16. package/dist/auth/authority.d.ts +55 -0
  17. package/dist/auth/authority.js +16 -0
  18. package/dist/auth/dpop.d.ts +15 -0
  19. package/dist/auth/dpop.js +49 -0
  20. package/dist/browser.d.ts +44 -0
  21. package/dist/browser.js +110 -0
  22. package/dist/conversation/service.d.ts +126 -0
  23. package/dist/conversation/service.js +754 -0
  24. package/dist/conversation/store.d.ts +26 -0
  25. package/dist/conversation/store.js +14 -0
  26. package/dist/feedback.d.ts +13 -0
  27. package/dist/feedback.js +44 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.js +7 -0
  30. package/dist/mcp.d.ts +7 -0
  31. package/dist/mcp.js +14 -0
  32. package/dist/network/discovery.d.ts +101 -0
  33. package/dist/network/discovery.js +276 -0
  34. package/dist/network/transport.d.ts +31 -0
  35. package/dist/network/transport.js +305 -0
  36. package/dist/protocol/sse.d.ts +15 -0
  37. package/dist/protocol/sse.js +90 -0
  38. package/dist/protocol/wire.d.ts +128 -0
  39. package/dist/protocol/wire.js +278 -0
  40. package/dist/reference-auth/index.d.ts +209 -0
  41. package/dist/reference-auth/index.js +999 -0
  42. package/dist/reference-auth/store.d.ts +53 -0
  43. package/dist/reference-auth/store.js +2 -0
  44. package/dist/sqlite-init.d.ts +2 -0
  45. package/dist/sqlite-init.js +17 -0
  46. package/dist/sqlite.d.ts +27 -0
  47. package/dist/sqlite.js +369 -0
  48. package/dist/store.d.ts +1 -0
  49. package/dist/store.js +1 -0
  50. package/examples/quickstart.mjs +29 -0
  51. package/package.json +67 -4
package/API.md ADDED
@@ -0,0 +1,21 @@
1
+ # API reference
2
+
3
+ All public entry points ship TypeScript declarations in `dist`. Use editor
4
+ completion for complete options and results; this map identifies the integration seams.
5
+
6
+ | Entry point | Main exports | Guide |
7
+ | --- | --- | --- |
8
+ | `@poppykit/server` | `createRequestAuthenticator`, `AuthorityError`, authority types | [authentication](AUTH.md) |
9
+ | `@poppykit/server` | `createConversationService`, `createConversationNotifications` | [delivery](DELIVERY.md) |
10
+ | `@poppykit/server` | `createBrowserSessions`, `createFeedbackHandler`, MCP metadata/error helpers | [integrations](INTEGRATIONS.md) |
11
+ | `@poppykit/server/reference-auth` | `createReferenceIssuer`, `createMetadataResolver`, `createNodeTransport` | [authentication](AUTH.md) |
12
+ | `@poppykit/server/sqlite` | `SqliteCompanyDatabase` | [storage](STORAGE.md) |
13
+ | `@poppykit/server/adapter-contract` | adapter verification helpers | [storage](STORAGE.md) |
14
+
15
+ Conversation workers claim accepted work, enforce account/scopes, publish replies
16
+ and finish work through service operations. External effects require application
17
+ idempotency and reconciliation. Existing issuers implement `ExternalAuthorityAdapter`
18
+ and do not need the optional reference issuer. Your framework owns HTTPS routing,
19
+ trusted public URLs, ingress limits, request cancellation and private-error handling.
20
+
21
+ Run the [quickstart](examples/quickstart.mjs) without a listener or external service.
package/AUTH.md ADDED
@@ -0,0 +1,60 @@
1
+ # Company authentication
2
+
3
+ Integrating an existing issuer uses the core `ExternalAuthorityAdapter` and
4
+ `createRequestAuthenticator`. It does not import, run, or persist the optional
5
+ reference issuer. The host must describe its issuer's revocation visibility and
6
+ order authorization checks with protected work. The reference issuer is a
7
+ separate `/reference-auth` import and uses the shared company database coordinator
8
+ for immediate revocation visibility.
9
+
10
+ Reference Direct sign-in uses `beginAuthorization` / `completeAuthorization`.
11
+ The host authenticates the human and protects consent with its own browser
12
+ session/CSRF controls; pass a high-entropy browser binding. Registered callback
13
+ URLs must be HTTPS on the client metadata domain and exactly match registration.
14
+
15
+ Enable reference Device sign-in with `device: { endpoint, verificationUri,
16
+ scopes }`. Route the configured device endpoint to `deviceAuthorization`.
17
+ On the human page call `beginDeviceAuthorization(userCode, browserBinding)` and
18
+ show the client identity and requested scopes. After an authenticated explicit
19
+ approve/decline action call `completeDeviceAuthorization`. Opening a link must
20
+ never approve it. The token endpoint handles durable polling intervals, slowdown,
21
+ denial, expiry, partial consent, one-use codes and the Session's sticky account.
22
+
23
+ Enable Mediated sign-in with `mediated: { endpoint, scopes, fields,
24
+ authenticate, verifyCode }` and route that endpoint and its sign-in-ID children
25
+ to `mediatedSignIn`. The SDK authenticates the Session Token and DPoP proof,
26
+ validates declared fields, counts attempts before calling the host backend,
27
+ and binds challenges to the initiating client, UserID and Session. The host
28
+ verifies credentials in `authenticate` and may return an account plus a narrower
29
+ scope list. It may instead return a challenge handle and safe delivery hint,
30
+ then verify codes using `verifyCode`. Host hooks must not log or retain credentials
31
+ and must enforce their own backend security and OTP delivery/verification.
32
+
33
+ Credentials and codes exist only during the trusted handler call; the SDK clears
34
+ input objects on exit, stores no passwords/codes and never includes them in
35
+ responses. Backend errors return a safe unavailable code. Pending remote hooks
36
+ are not automatically repeated after interruption. An operation left verifying
37
+ expires; the user can explicitly start another sign-in subject to attempt limits.
38
+
39
+ Defaults are five credential attempts per Session per minute, 100 per client per
40
+ minute, five code attempts per challenge, and ten-minute sign-in expiry. Configure
41
+ limits for your service and apply network-level limits before authentication.
42
+ SQLite persists attempt windows and bounded tombstones; `cleanup` removes sign-in
43
+ records only after their original expiry plus 30 seconds. A failed/expired code
44
+ continues returning the same terminal status through that retained window.
45
+
46
+ Each pending request captures a client revision. Account disconnect increments
47
+ the revision and records that account's fence; later credential/consent results
48
+ for that account cannot undo disconnect. Requests for other accounts sharing the
49
+ same personal-agent client continue. A fresh explicit sign-in captures the new
50
+ revision. Revision and per-account fence records remain until explicit issuer
51
+ reset and never count as pending requests. Terminal method tombstones also do
52
+ not consume pending quota.
53
+
54
+ Issuer state is version 3. Older development state is refused without mutation;
55
+ there is no automatic migration. Follow the explicit stop/backup/reset procedure
56
+ in [STORAGE.md](STORAGE.md) only for state you choose to discard.
57
+
58
+ Advertise only configured methods in your `poppy.json`; `authorizationMetadata()`
59
+ adds the device endpoint only when enabled. There is no login/consent UI, identity
60
+ provider, CLI or application workflow in this package.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ Documentation refresh: registry installation, runnable quickstarts, focused guides
6
+ and the tagged GitHub/OIDC release runbook. No runtime API, storage format or
7
+ third-party dependency change.
8
+
9
+ ## 0.1.0
10
+
11
+ Initial MIT development release, published manually. Discovery, safe HTTPS, all
12
+ three sign-in methods, targeted durable storage and recovery, conversations and
13
+ API/browser/feedback hooks. The CLI uses the client SDK with trusted secure-backend
14
+ adapters. J8 established strict typed tests and exact installed-artifact checks.
15
+
16
+ Version 0.x may still change APIs/storage; future changes must explain compatibility,
17
+ migration/reset and security implications. No stable 1.0 or external-host certification
18
+ is promised. Older private 0.0.0 candidates retain their own historical evidence.
@@ -0,0 +1,38 @@
1
+ # Contributing
2
+
3
+ Follow the live draft 0.2 [specification](https://personalagentprotocol.org/docs/0.2/spec)
4
+ and [guides](https://personalagentprotocol.org/docs/0.2/guides). Tests/snapshots are
5
+ supporting evidence. Demo/business workflows and individual extensions are excluded.
6
+ Code is [MIT](LICENSE); confirm rights for third-party contributions.
7
+
8
+ Use Node 24.12.0, TypeScript 5.9.3 and @types/node 24.10.0.
9
+ Install the packageManager-pinned pnpm and run pnpm install --frozen-lockfile.
10
+
11
+ ```sh
12
+ npm run typecheck
13
+ npm run build
14
+ npm run typecheck:tests
15
+ npm test
16
+ npm run check:docs
17
+ ```
18
+
19
+ First-party tests/workers/verification helpers are canonical `.mts`. The existing
20
+ compiler checks the whole strict program before emitting ignored adjacent `.mjs`.
21
+ Failed builds remove stale outputs; no loader/type stripping substitutes for checking.
22
+ Run `npm run build:tests` before `node scripts/run-checks.mjs GROUP` (unit, contracts,
23
+ integration, validation). Groups are exhaustive/disjoint. LF source bytes are enforced
24
+ by Git attributes. Generated test code is outside the npm allowlist; production tests
25
+ import built/installed JavaScript. Release/build tools and examples may remain JS.
26
+
27
+ `npm run coverage` builds production and checked tests, then writes native Node
28
+ line/branch/function reports under `coverage/` (summary.txt, lcov.info, report.json,
29
+ raw/). Metrics cover loaded first-party dist JS: no production source maps or TS
30
+ remapping; node_modules excluded, unloaded modules listed. Raw subprocess reports
31
+ are retained, but arbitrary children may not merge and killed workers may not flush.
32
+ Installed/OS/host checks are separate evidence; no percentage proves conformance.
33
+
34
+ Include callers, meaningful regression evidence and docs with behavior changes.
35
+ Preserve credential/destination validation, private errors and persisted data.
36
+ Report checks actually run and limits. Use Issues for non-sensitive defects and
37
+ [SECURITY.md](SECURITY.md) for vulnerabilities. Follow [RELEASING.md](RELEASING.md)
38
+ before tagging/publishing; obtain explicit owner authorization.
package/DELIVERY.md ADDED
@@ -0,0 +1,77 @@
1
+ # Conversation delivery and transport
2
+
3
+ The SDK targets Personal Agent Protocol draft 0.2. Company content, messages,
4
+ unknown events and partial text remain untrusted data for the host application.
5
+
6
+ ## Client interoperability
7
+
8
+ Clients keep immutable retry targets/message IDs, explicitly acknowledge named
9
+ consumers and treat cursor_expired as a history gap, not recovered history. A
10
+ disconnected client cannot undo accepted work. The [client delivery guide](https://github.com/LMol-4/poppykit-client/blob/main/DELIVERY.md)
11
+ specifies watch/reconnect bounds, partial text, pagination and checkpoint handling.
12
+
13
+ ## Node transport
14
+
15
+ `createNodeTransport` uses Node HTTPS agents, with separate discovery, JSON and
16
+ stream pools. Defaults: 8 pools total, 2 connections per pool (including pending
17
+ TLS), 32 active operations, 15-second JSON timeout, and 5-second discovery timeout.
18
+ There is no hidden request queue: excess pool capacity fails `overloaded`, so a
19
+ later attempt performs fresh DNS and policy checks. Hosts decide scheduling.
20
+ Streams use `transport.stream` with a 5-minute/16-MiB lifetime budget. Call
21
+ `transport.close()` to cancel outstanding reads, DNS waiters, connects and streams.
22
+
23
+ Every request rechecks DNS and current address policy before pool assignment.
24
+ Changed approved addresses retire incompatible pools; actual TLS peers and reused
25
+ sockets are checked before HTTP bytes. TLS certificates remain verified. Only
26
+ explicit hostname/address exceptions permit private peers. Discovery never carries
27
+ credentials and follows only HTTPS redirects; credential-bearing requests never
28
+ follow redirects. Pool retirement may interrupt an active stream; reconnect uses
29
+ the durable cursor and fresh authorization.
30
+
31
+ Metadata caching remains bounded and honors freshness, Age, validators, no-cache
32
+ and no-store, with a 300-second upper bound. Expired entries revalidate; 304s retain
33
+ prior caching directives. `resolver.invalidate(target?)` fences old pending cache
34
+ writes without bypassing fetch concurrency/rate limits. The auth lifecycle rejects changed saved
35
+ issuer/endpoint/resource bindings rather than rerouting persisted work.
36
+
37
+ ## Company service and retention
38
+
39
+ The server accepts `supportedScopes`, the union offered by its actual sign-in
40
+ methods. `directScopes` remains a compatibility alias. One coordinator must still
41
+ cover authority checks and durable conversation reads/mutations.
42
+
43
+ Default JSON/request/frame budget is 1 MiB; the maximum encoded event is
44
+ 1 MiB minus 1025 bytes. Page sizing reserves 1024 bytes for worst-case IDs, cursors
45
+ and envelopes and counts UTF-8 bytes. Company events and client messages that
46
+ cannot fit are rejected before commit; no receipt/work survives a rejected write.
47
+ Do not reduce configured budgets below those used for retained accepted events.
48
+
49
+ Notifications are post-commit hints. Subscribe before reading; reconcile durable
50
+ state on wake or every 1000 ms by default. `createConversationNotifications` is
51
+ local acceleration only. Multiple processes work without a broker through targeted
52
+ SQLite reconciliation; deployments can implement the same notification contract
53
+ with their own distributed adapter. Lost/duplicate hints cannot erase a commit.
54
+ SQLite event windows and append operations do not reload complete event histories.
55
+
56
+ Server streams default to 30 seconds and apply pull backpressure, rechecking
57
+ current authority before further pages. This bounded-connection policy omits
58
+ periodic idle comments; proxies may close an idle stream sooner, and the client
59
+ reconnects. Longer-lived deployments should supply heartbeat handling at their
60
+ HTTP adapter. Hosts must propagate disconnect/AbortSignal and body cancellation.
61
+
62
+ | Record | Lifetime / deletion rule |
63
+ | --- | --- |
64
+ | Conversation and message receipts | Entire conversation lifetime, including after closure and payload pruning; no automatic deletion |
65
+ | Event payloads | Retained indefinitely by default; host may call SQLite `pruneEvents(id, before, limit)` in bounded batches; recommend at least 30 days |
66
+ | Event cursor identities | Conversation lifetime; distinguish expired payloads (410) from foreign cursors (400) |
67
+ | Active work | Never pruned while held/pending/claimed; event pruning protects these conversations |
68
+ | Completed work | Bounded inspection retention; stale fences cannot publish |
69
+ | Latest user message, context and account binding | Operational conversation record, retained for its lifetime, including handoff after payload pruning |
70
+ | Client outbound/checkpoints | Retained until explicit local store deletion/reset; preserve pending/uncertain sends until reconciled |
71
+ | Pending auth and replay records | Separate auth validity/owner rules in AUTH.md and STORAGE.md; not the event TTL |
72
+ | CLI transcripts/retries | CLI ownership; local deletion never deletes company history or revokes authorization |
73
+
74
+ Pruning is explicit operator policy, never automatic data loss. It preserves old
75
+ message receipts and cursor identity. Concurrent reads see a coherent snapshot or
76
+ `cursor_expired`. Invalid/expired waited-POST cursors are rejected before accepting
77
+ the new write; an existing message ID still returns its original receipt.
@@ -0,0 +1,84 @@
1
+ # API, browser and feedback integration
2
+
3
+ Use the same authority policy and coordinator as protected conversation work.
4
+ Supply the trusted public request URL; never derive it from arbitrary forwarding
5
+ headers. Custom issuers and transports remain responsible for their documented
6
+ revocation, address-validation and storage guarantees.
7
+
8
+ ## MCP authentication
9
+
10
+ ```ts
11
+ const metadata = mcpResourceMetadata({ resource: mcpUrl, issuer, dpop: true });
12
+ const authenticator = createRequestAuthenticator({ authority, now: Date.now,
13
+ endpoints: [{ origin: 'https://company.example', pathPrefix: '/mcp', resource: mcpUrl,
14
+ mcp: { dpop: true, metadataUrl: 'https://company.example/.well-known/oauth-protected-resource/mcp' } }],
15
+ });
16
+ // Serve metadata publicly without credentials. In the existing MCP handler:
17
+ try {
18
+ const principal = await authenticator.authenticate(request);
19
+ return await authority.policy.withCurrent(principal, ['tools:read'], () => handleMcp(request));
20
+ } catch (error) {
21
+ if (error instanceof AuthorityError) return mcpAuthorizationError(error, metadataUrl, true);
22
+ throw error;
23
+ }
24
+ ```
25
+
26
+ MCP metadata identifies the exact resource URL, issuer and ES256 support when
27
+ enabled. Challenges include Bearer and, if supported, DPoP with `resource_metadata`.
28
+ The exact endpoint accepts no child paths or added query. All other APIs retain
29
+ DPoP-only authentication. Configure the reference issuer `mcpResources` with the
30
+ same `{url,dpop}` list: every grant chooses Bearer for a non-DPoP MCP server even
31
+ when a proof was provided. DPoP MCP requires a valid proof. Bearer tokens are
32
+ resource-bound; DPoP-bound tokens cannot be presented as Bearer.
33
+
34
+ An existing issuer can accept ordinary MCP OAuth clients through its own flow.
35
+ Its introspector returns `dpopJkt: null` only for truly unbound Bearer credentials;
36
+ ordinary MCP principals have empty `userId` and an account-bound session owned by
37
+ the MCP client. Such sessions end on sign-in revocation/expiry and must not
38
+ continue signed out. The external issuer must enforce this on every introspection
39
+ and policy check and document its revocation visibility. The reference issuer
40
+ implements personal-agent grants; it does not add a general OAuth login product.
41
+
42
+ ## Browser sessions
43
+
44
+ ```ts
45
+ const browser = createBrowserSessions({ endpoint: browserEndpoint, domain: companyDomain,
46
+ metadata, authority: referenceIssuer.browserSessions,
47
+ policy: referenceIssuer.authority.policy, cookies: cookieStore, now: Date.now,
48
+ });
49
+ // Mount browser.join on browserEndpoint; no request-body logging at this route.
50
+ const result = await browser.withSession(request, ['records:read'], principal => readRecords(principal));
51
+ ```
52
+
53
+ `BrowserCookieStore.put/get` stores SHA256 cookie-value hashes, session bindings and
54
+ expiry. Supply durable storage with targeted operations and expire old rows. Never
55
+ store plaintext cookie values. `BrowserSessionAuthority.join` must atomically
56
+ consume assertion jti and validate an active same-client/same-user session; `current`
57
+ must read current session permissions. The reference issuer implements both on its
58
+ existing durable issuer store/coordinator; SQLite uses targeted browser selectors.
59
+
60
+ The browser handler verifies JWKS signature, asymmetric algorithm, typ, exact aud,
61
+ times/lifetime, replay, client/user/session and HTTPS company return URL. Invalid
62
+ requests return 400 with no cookie; successful requests set a host-only cookie and
63
+ return 303 to the validated URL. Defaults are `__Host-poppy-session`, Path=/, Secure,
64
+ HttpOnly, SameSite=Lax and max 3600 seconds capped by the current session lifetime.
65
+ SameSite=None is supported; Strict is refused. `authenticate` resolves current
66
+ authority on every request. Use `withSession` for scoped mutations so permission
67
+ checks and work are ordered by the same policy/coordinator. Sign-in, scope changes
68
+ and revocation are reflected immediately; no permissions are cached in cookies.
69
+ Host adapters must isolate the agent browser from the user's consent browser.
70
+
71
+ ## Feedback
72
+
73
+ `createFeedbackHandler({endpoint, authenticator, policy, accept, allow?})` validates
74
+ a Session Token, including a signed-out token with no account scope. It checks the
75
+ JSON/UTF8 byte budget and nonempty task/description limits (200/2000 Unicode
76
+ codepoints), accepts unknown kinds and passes only a frozen inert-data report to
77
+ `accept`. Do not interpret its text as instructions, including in later model use.
78
+ The optional host `allow` hook supplies rate limiting; false returns 429. Malformed
79
+ reports return 400, auth errors use the shared challenge, success is empty 202.
80
+ There is no protocol deduplication identifier and no exactly-once promise. Hosts
81
+ own report retention, privacy filtering and downstream side-effect handling.
82
+
83
+ `tests/integrations.test.mts` exercises browser replay/current authority in memory
84
+ and SQLite, MCP schemes/resources/ordinary-client integration and feedback.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PoppyKit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,68 @@
1
- # Temporary Holding Version
1
+ # PoppyKit server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript SDK for companies implementing [Personal Agent Protocol draft 0.2](https://personalagentprotocol.org/docs/0.2/spec).
4
+ Verify requests, serve conversations and enforce current authority. Your company
5
+ supplies accounts, consent UI, agent, business tools and HTTPS routing. [MIT](LICENSE).
6
+
7
+ ## Install
8
+
9
+ Requires Node **24.12.0+** and ESM. CI tests Node 24.12.0 on Linux, macOS and Windows;
10
+ later majors are not separately certified. SQLite is optional/experimental and
11
+ requires local filesystem locking.
12
+
13
+ ```sh
14
+ npm install @poppykit/server@0.1.1
15
+ ```
16
+
17
+ ## Configure a service
18
+
19
+ This runnable example starts no listener and rejects an unauthenticated request.
20
+ `:memory:` is non-durable; use a private file and configured company sign-in hooks
21
+ in production. The reference issuer is optional; [existing issuers](AUTH.md) use
22
+ an ExternalAuthorityAdapter instead.
23
+
24
+ ```js
25
+ import { createRequestAuthenticator, createConversationService } from '@poppykit/server';
26
+ import { createMetadataResolver, createNodeTransport, createReferenceIssuer } from '@poppykit/server/reference-auth';
27
+ import { SqliteCompanyDatabase } from '@poppykit/server/sqlite';
28
+
29
+ const database = new SqliteCompanyDatabase(':memory:');
30
+ const transport = createNodeTransport();
31
+ try {
32
+ const issuer = 'https://company.example/issuer';
33
+ const endpoint = 'https://company.example/conversations';
34
+ const auth = createReferenceIssuer({
35
+ issuer, tokenEndpoint: issuer + '/token', revocationEndpoint: issuer + '/revoke',
36
+ poppyDomains: ['company.example'], directScopes: [], resources: [endpoint],
37
+ metadata: createMetadataResolver(transport), store: database.issuer(issuer),
38
+ coordinator: database, now: Date.now,
39
+ });
40
+ const service = createConversationService({
41
+ endpoint, store: database.conversations(issuer), policy: auth.authority.policy,
42
+ authenticator: createRequestAuthenticator({ authority: auth.authority,
43
+ endpoints: [{ origin: 'https://company.example', pathPrefix: '/conversations', resource: endpoint }],
44
+ now: Date.now }), clock: { now: Date.now },
45
+ });
46
+ console.log((await service.handle(new Request(endpoint, { method: 'POST',
47
+ headers: { 'content-type': 'application/json' }, body: '{}' }))).status); // 401
48
+ } finally {
49
+ transport.close?.();
50
+ database.close();
51
+ }
52
+ ```
53
+
54
+ ## Guides
55
+
56
+ - [Authentication](AUTH.md): external/reference issuers, consent and revocation.
57
+ - [Storage](STORAGE.md): shared authority coordinator, cross-process transactions and reset.
58
+ - [Delivery](DELIVERY.md): workers, streaming, cursors, retention and effect idempotency.
59
+ - [Integrations](INTEGRATIONS.md): MCP, browser cookies and feedback.
60
+ - [API exports](API.md), [contributing](CONTRIBUTING.md), [security](SECURITY.md),
61
+ [changelog](CHANGELOG.md) and [releasing](RELEASING.md).
62
+
63
+ Your ingress must bound bodies/rates before authentication, supply the trusted
64
+ public URL and propagate cancellation. Never trust arbitrary forwarding headers.
65
+ Use current policy before protected work; model text cannot grant permission.
66
+ Receipt/publication deduplication does not make external effects exactly-once.
67
+ Validate deployed database, issuer and browser policies; no model/service vendor
68
+ or demo application is required.
package/RELEASING.md ADDED
@@ -0,0 +1,91 @@
1
+ # Releasing
2
+
3
+ Use the tagged GitHub Actions workflow, not a local rebuild. Packages are MIT;
4
+ 0.1.0 was manually bootstrapped. Version 0.1.1 refreshes documentation without
5
+ changing runtime APIs. Each new version needs its own source/artifact evidence.
6
+
7
+ ## Before tagging
8
+
9
+ - Confirm npm ownership, unused versions, supported Node 24.12.0 and npm>=11.5.1.
10
+ Keep the pinned compiler/dependencies; do not upgrade tooling during a release.
11
+ - Update version, changelog and shipped docs. CLI's client dependency and both
12
+ candidate JSON files must match exact final SDK versions/full source SHAs.
13
+ - Review the full diff and run typecheck, build, tests and documentation checks.
14
+ Preserve the existing API/storage/security contracts and historical evidence.
15
+ - Protect source, workflow files, pins and release tags with repository review/rules.
16
+ All actions are commit-pinned; release verification never restores a PR cache.
17
+ - Verify GitHub environment **npm**: variable **NPM_PUBLISH_ENABLED=true**,
18
+ intended required reviewer, no admin bypass, and allowed deployment tags **v***.
19
+ Reviewers approve the real pending deployment; automation must not approve for them.
20
+ - Configure the npm package's GitHub trusted publisher: owner **LMol-4**, this
21
+ package repository, workflow **release.yml**, environment **npm**. Match case.
22
+ Enable **direct npm publish** permission; this workflow does not stage a publish.
23
+ Configure near the release: unused configurations expire after two days. Public
24
+ source/package visibility and supported GitHub-hosted runners are required for
25
+ this provenance path. Do not supply NODE_AUTH_TOKEN or fall back to a long-lived
26
+ token if OIDC fails. Confirm current [npm requirements](https://docs.npmjs.com/trusted-publishers/).
27
+
28
+ ## Publish exact tagged source
29
+
30
+ Client and server are independent. Publish client before CLI: the CLI registry
31
+ guard requires its tested client tarball integrity to equal the published one.
32
+
33
+ 1. Merge reviewed source to main. Record the full SHA and create/push an approved
34
+ immutable **vVERSION** tag at that exact commit. Never move or replace a tag.
35
+
36
+ ```sh
37
+ git tag vVERSION FULL_SHA
38
+ git push origin refs/tags/vVERSION
39
+ ```
40
+ 2. Dispatch **Release** from that tag, with matching version, tag and full commit.
41
+ Optional `publish=false` rehearses verification only. A later publishing dispatch
42
+ is a new run with newly identified artifacts; it does not reuse a rehearsal's bytes.
43
+ 3. Dispatch with `publish=true` when authorized. Example (replace all placeholders):
44
+
45
+ ```sh
46
+ gh workflow run release.yml --repo LMol-4/PACKAGE --ref vVERSION -f version=VERSION -f tag=vVERSION -f commit=FULL_SHA -f publish=true
47
+ ```
48
+
49
+ 4. Wait for candidate, fast, contracts, integration, packaged-linux/windows/macos,
50
+ validation and gate to succeed. Missing, failed, canceled or skipped prerequisites
51
+ fail closed. Then follow the Actions environment-approval link. Do not bypass it.
52
+ 5. The publisher rechecks source/tag/ref/version, trusted manifest digest, unchanged
53
+ tarball metadata/bytes, owner enablement, registry absence and dependency integrity.
54
+ It publishes the same-run tested tarball with `--access public --provenance
55
+ --ignore-scripts`; it never installs, builds or packs again.
56
+ 6. Save run URL, full source SHAs, manifest SHA256, filenames, versions, byte counts,
57
+ tarball SHA256/npm SHA512 and registry integrity/provenance. Download before the
58
+ 14-day candidate retention expires. Verify registry bytes, a clean exact-version
59
+ registry install, exported modules and the real npm CLI shim.
60
+
61
+ Ordinary main/tag CI is useful evidence but is not a tagged Release/OIDC publishing
62
+ run. A dry run or green tests alone do not prove npm authentication or provenance.
63
+ The separate checked test-harness artifact is source/output-hash bound, used only
64
+ by unprivileged verification jobs, and never published in an npm package.
65
+
66
+ ## Failure and partial release
67
+
68
+ Stop on tag/source/version/digest mismatch, unknown registry responses or changed
69
+ client integrity. Do not weaken gates, repack approved bytes, change pins silently,
70
+ force an overwrite or use a token fallback. Inspect the failed job and preserve logs.
71
+ For OIDC/auth failure check publisher spelling/case, workflow/environment identity,
72
+ expiry, reviewer approval, permissions and npm/toolchain support before a new run.
73
+
74
+ If a version already exists, compare its registry integrity and provenance with the
75
+ saved receipt. Matching successful publication is recorded, never republished.
76
+ For partial publication, finish only absent packages; verify the published client's
77
+ exact bytes before CLI. If bytes differ or a fix is needed, use a reviewed new
78
+ version and complete verification. A source checkout rollback cannot undo an npm
79
+ publication or external side effect. Deprecation, dist-tag changes, unpublish and
80
+ replacement versions need separate owner authorization; never delete evidence.
81
+
82
+ ## Verification limits
83
+
84
+ Deterministic source suites run once per verification graph on Linux; exhaustive
85
+ file groups reject omissions/duplicates. Packaged jobs test actual installed JS,
86
+ exports/declarations/content/SQLite and representative platform behavior; CLI also
87
+ tests real shims, auth/retry processes and API/browser/feedback wiring. They do not
88
+ certify a production keyring, physical terminal, browser engine, KMS or TLS deployment.
89
+ Windows GNU tar is given archive basename plus directory cwd to avoid drive-colon
90
+ remote parsing. Failure logs retain seven days. Expired/missing artifacts require a
91
+ new full run, not relabeling old results. See [CONTRIBUTING.md](CONTRIBUTING.md).
package/SECURITY.md ADDED
@@ -0,0 +1,13 @@
1
+ # Security reporting
2
+
3
+ Do not put credentials, tokens, private keys, personal records or exploit details
4
+ in a public issue. Use this repository's GitHub **Security → Report a vulnerability**
5
+ when private reporting is enabled. If it is unavailable, request a private contact
6
+ from the repository owner without disclosing the vulnerability publicly. The owner
7
+ must enable/confirm that route; no response SLA is promised.
8
+
9
+ Include affected commit/version, a minimal reproduction using synthetic data,
10
+ impact and the trust boundary involved. These draft packages have no promised
11
+ support window or response SLA. Custom vaults, issuers, browser controllers,
12
+ proxy routing and deployment policy remain operator responsibilities. Redaction
13
+ and scoped authority do not isolate an unrestricted same-user program.
package/STORAGE.md ADDED
@@ -0,0 +1,112 @@
1
+ # Company persistence and authorization
2
+
3
+ Request paths use targeted domain selections, not whole-state rewrites. Node 24.12.0 or later is required.
4
+ The optional `@poppykit/server/sqlite` entry point exports `SqliteCompanyDatabase`.
5
+ The core and reference issuer entry points do not import SQLite. Consumers can
6
+ implement `ConversationStore`, `IssuerStore` and `AuthorityCoordinator` themselves.
7
+ The tested Node 24.12.0 runtime marks `node:sqlite` experimental.
8
+
9
+ ```ts
10
+ import { SqliteCompanyDatabase } from '@poppykit/server/sqlite';
11
+ const database = new SqliteCompanyDatabase('/private/path/company.db');
12
+ const issuer = 'https://company.example/issuer';
13
+ const issuerStore = database.issuer(issuer);
14
+ const conversationStore = database.conversations(issuer);
15
+ // Reference issuer options: store: issuerStore, coordinator: database.
16
+ // Conversation options: store: conversationStore, policy: referenceIssuer.authority.policy.
17
+ // Close database only after all in-flight requests/work have ended.
18
+ ```
19
+
20
+ Use the same database instance for the reference issuer, its coordinator, and its
21
+ conversation storage. A policy check and the protected conversation mutation join
22
+ one SQLite transaction. Revocation/rotation/acceptance cannot interleave between
23
+ authorization and commit. External issuers still supply their own policy and must
24
+ state revocation latency and how their checks are ordered with protected writes.
25
+ Never replace the coordinator with a process-local mutex for a multi-process service.
26
+
27
+ SQLite uses WAL and FULL synchronous commits. A database serializes writers; this
28
+ reference adapter does not promise parallel SQLite writers. The short per-instance
29
+ queue protects its single connection, and SQLite coordinates separate processes.
30
+ There is no application-wide static lock: separate databases are independent. The
31
+ coordinator is a database transaction boundary, not a lock held during external
32
+ model, vault, or HTTP calls. SQL busy retries are bounded. Use a local filesystem
33
+ with correct SQLite locks; network-mounted multi-host SQLite is unsupported.
34
+
35
+ ## Domain records and guarantees
36
+
37
+ Every primary key includes a namespace (use the exact full issuer). Sessions,
38
+ grants, current/previous token hashes, authorization requests, codes, access-token
39
+ hashes, replay keys, conversation metadata, shared account bindings, message
40
+ receipts and work intent are separate records. Ordered events are a separate table
41
+ keyed by namespace/conversation/sequence. Indexes cover client, account, grant,
42
+ conversation, binding, phase and expiry. User identity and authorization checks
43
+ still apply inside the SDK; a namespace name alone is not authentication.
44
+
45
+ Conversation selections resolve an ID, message receipt, work ID, or next eligible
46
+ claim. Reads include the related parent/Direct Conversation binding group, not
47
+ unrelated conversations. A worker query selects the oldest eligible message for a
48
+ conversation; publication rechecks lease and fence. Cursor-relative event selectors bound delivery reads without loading work/history
49
+ aggregates. `ConversationNotifications` defines subscribe/unsubscribe/publish:
50
+ subscribe before reading, treat wakes as hints, and always reconcile durable state
51
+ after a wake or bounded timeout. Publishing after commit is best-effort, not an outbox.
52
+
53
+ Issuer selections address metadata, token admission, a principal, a token hash, an
54
+ authorization, or a client/account disconnect. Session and authorization limits
55
+ use indexed counts rather than loading other clients' records. Related grant
56
+ sessions are loaded when revocation must sign them out atomically.
57
+
58
+ All mutations run synchronous callbacks over detached domain records. Async
59
+ callbacks and non-JSON values are rejected. Exceptions roll back; domain savepoints
60
+ also roll back if an enclosing policy caller catches an error. Returned values and
61
+ retained callback objects cannot mutate persisted state after commit. Only changed
62
+ rows and newly appended events are written. Conversation deletion, event rewriting,
63
+ and generation reset are not silently accepted through a targeted transaction.
64
+
65
+ Acceptance, event append, message deduplication receipt and pending work commit in
66
+ one transaction. There is no second outbox. The same message ID with different
67
+ content/target conflicts. Claim recovery can execute application work again after
68
+ a lease expires: the fence prevents stale SDK publication, but cannot undo or
69
+ deduplicate arbitrary external effects. The application's downstream system needs
70
+ its own idempotency key (such as the stable work ID), or the application must retain
71
+ an uncertain result for reconciliation. Never claim universal exactly-once execution.
72
+
73
+ `cleanup(namespace, now, limit)` bounds deletion to at most 1000 expired short-lived
74
+ codes, authorizations, access tokens or replay entries. It does not delete grants,
75
+ work intent, message receipts or conversation events. Receipt identity remains
76
+ through retention for the conversation lifetime. Finished work pruning is
77
+ per selected conversation binding group; unrelated conversation growth cannot
78
+ force another conversation's receipts out of storage.
79
+
80
+ ## Non-durable use and resets
81
+
82
+ `new SqliteCompanyDatabase(':memory:')` is an explicit non-durable adapter for tests
83
+ and development. Each instance is separate, is lost at close/process death, and
84
+ offers no cross-process persistence. The test fixture's object store is also
85
+ non-durable and is not evidence of database atomicity.
86
+
87
+ No old whole-state files are migrated or deleted automatically. For a disposable
88
+ development reset, stop all processes, resolve uncertain external effects, and
89
+ explicitly move aside the database and WAL/SHM files. Reopen a fresh database to
90
+ obtain new generations. Never reset a live database behind active processes.
91
+
92
+ Contract tests cover real process contention/death, publication fences, rotation/
93
+ revocation, rollback, isolation and targeted read counts; see [contributing](CONTRIBUTING.md).
94
+
95
+ ## Bounded conversation delivery and retention
96
+
97
+ `ConversationSelection.events` loads a cursor-relative, count/byte-bounded event
98
+ window without work/history aggregates; it is read-only inside transactions.
99
+ `append` loads operational records without historical event payloads. `cursorCheck`
100
+ checks retained cursor identities for waited POSTs. These selectors preserve the
101
+ shared authority coordinator. Custom adapters must implement equivalent targeted
102
+ reads before claiming the same operation-count guarantees.
103
+
104
+ SQLite `pruneEvents(conversationId, beforeUnixMilliseconds, limit=100)` deletes at
105
+ most 1000 old payloads per call and refuses active held/pending/claimed work.
106
+ Message receipts, event cursor identities and the latest user message/context stay
107
+ with the conversation for its lifetime. Closure is not deletion. Retention uses
108
+ one database transaction; concurrent writers/readers see a coherent before/after
109
+ state. Default payload retention is indefinite; operators should retain at least
110
+ 30 days and explicitly choose any shorter policy. See [DELIVERY.md](DELIVERY.md)
111
+ for distinct lifetimes and client recovery behavior. No automatic destructive
112
+ reset or history deletion was added.
@@ -0,0 +1,12 @@
1
+ import type { ConversationOptions } from './conversation/service.js';
2
+ type Store = ConversationOptions['store'] & {
3
+ inspect(): Promise<import('./conversation/service.js').ConversationStoreState>;
4
+ adminTransaction<T>(change: (draft: import('./conversation/service.js').ConversationStoreState) => T): Promise<T>;
5
+ };
6
+ export type ConversationAdapterFixture = () => Promise<{
7
+ store: Store;
8
+ second: Store;
9
+ close(): Promise<void>;
10
+ }>;
11
+ export declare function runConversationAdapterContract(fixture: ConversationAdapterFixture): void;
12
+ export {};