@poppykit/client 0.1.0 → 0.2.0
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.
- package/API.md +4 -0
- package/CHANGELOG.md +15 -15
- package/CONTRIBUTING.md +30 -42
- package/DELIVERY.md +6 -25
- package/PROVIDER.md +25 -2
- package/README.md +26 -98
- package/RELEASING.md +91 -193
- package/SECURITY.md +3 -3
- package/STORAGE.md +65 -8
- package/dist/adapter-contract.d.ts +15 -0
- package/dist/adapter-contract.js +48 -0
- package/dist/postgres-runtime.d.ts +30 -0
- package/dist/postgres-runtime.js +172 -0
- package/dist/postgres.d.ts +39 -0
- package/dist/postgres.js +143 -0
- package/dist/provider-postgres.d.ts +15 -0
- package/dist/provider-postgres.js +96 -0
- package/dist/provider-sqlite.d.ts +2 -2
- package/dist/provider-sqlite.js +9 -6
- package/dist/provider-storage.d.ts +7 -0
- package/dist/provider-storage.js +61 -0
- package/dist/provider.d.ts +40 -10
- package/dist/provider.js +35 -20
- package/migrations/postgres/0001_poppy_client.sql +16 -0
- package/migrations/postgres/0001_poppy_provider.sql +14 -0
- package/package.json +26 -3
package/RELEASING.md
CHANGED
|
@@ -1,193 +1,91 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
environment settings, npm ownership or OIDC exchange work.
|
|
93
|
-
|
|
94
|
-
Any documentation/package-metadata/source change after J6 creates a new candidate.
|
|
95
|
-
J6 evidence applies only to J6's recorded commits/digests. Re-run this complete
|
|
96
|
-
graph on final commits/tarballs and retain the new manifests/platform results.
|
|
97
|
-
Never label changed bytes publishable using old evidence. A passing Windows
|
|
98
|
-
local run does not substitute for actual hosted Linux/macOS jobs.
|
|
99
|
-
|
|
100
|
-
## Owner configuration — external, not performed by this repository
|
|
101
|
-
|
|
102
|
-
Rechecked against [npm trusted publishers](https://docs.npmjs.com/trusted-publishers/),
|
|
103
|
-
[GitHub secure use](https://docs.github.com/en/actions/reference/security/secure-use),
|
|
104
|
-
[OIDC](https://docs.github.com/en/actions/concepts/security/openid-connect), and
|
|
105
|
-
[deployment environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments)
|
|
106
|
-
on **2026-10-23**. Recheck those live requirements before first publication:
|
|
107
|
-
|
|
108
|
-
1. The owner confirmed the `@poppykit` scope and selected MIT/version 0.1.0.
|
|
109
|
-
Confirm the actual publishing account has package-name/maintainer rights and
|
|
110
|
-
the chosen version is unused. The complete LICENSE is in the package allowlist
|
|
111
|
-
and release notes describe this initial development candidate.
|
|
112
|
-
Confirm repository.url exactly matches the actual GitHub repository.
|
|
113
|
-
2. Protect main, release tags, Actions files/scripts and candidate pins with
|
|
114
|
-
repository rules and review/CODEOWNERS appropriate to the actual team. Require
|
|
115
|
-
the complete verification result for the exact commit. Prevent unreviewed
|
|
116
|
-
workflow changes or tag replacement. Read-only PR permissions and trusted
|
|
117
|
-
publishing cannot enforce these owner settings themselves.
|
|
118
|
-
3. Create environment `npm` in each package repository with required reviewers,
|
|
119
|
-
self-approval restrictions, protected release-tag rules and no admin bypass
|
|
120
|
-
where the repository plan supports them. Confirm feature availability; an
|
|
121
|
-
unprotected environment name alone provides no approval barrier. Add environment
|
|
122
|
-
variable `NPM_PUBLISH_ENABLED=true` only after setup/authorization. Missing
|
|
123
|
-
or false configuration fails the publisher's check.
|
|
124
|
-
4. Configure each npm package's trusted publisher for GitHub owner `LMol-4`,
|
|
125
|
-
the corresponding `poppykit-client`, `poppykit-server` or `poppykit-cli`
|
|
126
|
-
repository, workflow filename **release.yml**, and environment **npm**.
|
|
127
|
-
Match spelling/case and the initiating workflow identity. verify.yml is a
|
|
128
|
-
reusable verifier, not the publishing workflow. Reassess identity if introducing
|
|
129
|
-
a reusable publisher or changing callers; workflow_call/dispatch identity has
|
|
130
|
-
documented caller nuances. This setup authorizes a workflow identity, not its
|
|
131
|
-
test results. The gate supplies the independent verification condition.
|
|
132
|
-
5. npm currently requires npm >=11.5.1 and Node >=22.14.0 for trusted publishing;
|
|
133
|
-
this workflow uses Node24.12.0 and checks npm's version without upgrading it.
|
|
134
|
-
GitHub-hosted runners are supported; self-hosted runners are not supported for
|
|
135
|
-
this npm path. GitHub permissions must permit OIDC. No NODE_AUTH_TOKEN is set.
|
|
136
|
-
Do not silently fall back to a long-lived publish token when OIDC fails.
|
|
137
|
-
6. Verify provenance requirements, repository visibility and package visibility
|
|
138
|
-
in the current npm documentation. Trusted publishing generates provenance
|
|
139
|
-
automatically for supported public packages/public repositories; explicit
|
|
140
|
-
--provenance must not be treated as proof without checking the registry record.
|
|
141
|
-
A private-source repository may need an owner-approved alternative or visibility
|
|
142
|
-
decision; do not claim provenance supported where it is not.
|
|
143
|
-
7. Current npm publisher configurations require first successful use within two
|
|
144
|
-
days or expire; configure near the authorized release. npm supports up to ten
|
|
145
|
-
configurations, with separate direct-publish/staging/dist-tag permissions.
|
|
146
|
-
Grant only this workflow's required direct publication capability, review other
|
|
147
|
-
publishers/tokens and restrict alternative access according to owner policy.
|
|
148
|
-
Keep recovery access separate from the normal workflow.
|
|
149
|
-
|
|
150
|
-
## First publication and bootstrap
|
|
151
|
-
|
|
152
|
-
A new package may need first publication using authorized npm-owner authentication
|
|
153
|
-
before package settings/trusted publishing are available. npm name availability
|
|
154
|
-
has not been established by creating a GitHub repository. The owner must confirm
|
|
155
|
-
the current bootstrap flow and explicitly authorize it. Do not place bootstrap
|
|
156
|
-
tokens in PR workflows or temporarily make publication unconditional.
|
|
157
|
-
|
|
158
|
-
Prepare a license/version-ready candidate, run the exact full verification graph,
|
|
159
|
-
retain/download the unchanged manifest/tarball and verify its digest. If the first
|
|
160
|
-
publication must be manual, an authorized owner uses their own current npm login/
|
|
161
|
-
2FA or narrowly scoped supported bootstrap credential to publish those exact bytes,
|
|
162
|
-
then removes temporary credentials and configures the trusted publisher. Record
|
|
163
|
-
the resulting version/integrity/provenance. A dry run is not proof of bootstrap
|
|
164
|
-
permissions or production OIDC authentication. Current 0.1.0 candidates have public-ready metadata, but publication still
|
|
165
|
-
requires all exact-byte checks, owner settings and separate authorization.
|
|
166
|
-
|
|
167
|
-
## Package order, existing versions and partial recovery
|
|
168
|
-
|
|
169
|
-
Release by actual production dependencies: client and server are independent;
|
|
170
|
-
either SDK can publish first. Publish the client before a CLI version requiring
|
|
171
|
-
that exact client version. The CLI registry guard requires the published client's
|
|
172
|
-
dist.integrity to match its tested candidate tarball. If it differs, stop and
|
|
173
|
-
reconcile/retest the actual dependency; do not silently replace tested bytes.
|
|
174
|
-
|
|
175
|
-
Before publishing, npm view must confirm that this package version is absent.
|
|
176
|
-
Only a structured E404 is considered absent; auth/network/registry failures stop.
|
|
177
|
-
An existing version stops publication even after an uncertain earlier response.
|
|
178
|
-
Inspect npm's published version, integrity, provenance and intended dist-tag using
|
|
179
|
-
read-only registry commands. Never overwrite or blindly republish. If the exact
|
|
180
|
-
bytes already published, record that success and resume the remaining package's
|
|
181
|
-
separate verified release. Multi-repository releases are not atomic. Do not unpublish
|
|
182
|
-
a successful dependency as rollback. If correction is required, choose a new
|
|
183
|
-
version, update dependent pins/lockfiles/release notes and verify a new candidate.
|
|
184
|
-
|
|
185
|
-
This runbook does not authorize publication, tag pushes, package ownership changes,
|
|
186
|
-
external settings, deployment or demo implementation.
|
|
187
|
-
|
|
188
|
-
Candidate metadata is inspected with the archive basename and its directory as
|
|
189
|
-
`cwd`. This avoids GNU tar interpreting a Windows drive colon as a remote archive;
|
|
190
|
-
stderr is preserved in failure diagnostics. Negative checks exercise GNU tar on
|
|
191
|
-
Windows with space/Unicode candidate paths and retain wrong/missing/changed bytes
|
|
192
|
-
and fail-closed gate cases. Local success does not certify a future hosted run or
|
|
193
|
-
any newly packed bytes.
|
|
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
CHANGED
|
@@ -4,10 +4,10 @@ Do not put credentials, tokens, private keys, personal records or exploit detail
|
|
|
4
4
|
in a public issue. Use this repository's GitHub **Security → Report a vulnerability**
|
|
5
5
|
when private reporting is enabled. If it is unavailable, request a private contact
|
|
6
6
|
from the repository owner without disclosing the vulnerability publicly. The owner
|
|
7
|
-
must enable/confirm that route
|
|
7
|
+
must enable/confirm that route; no response SLA is promised.
|
|
8
8
|
|
|
9
9
|
Include affected commit/version, a minimal reproduction using synthetic data,
|
|
10
|
-
impact and the trust boundary involved. These
|
|
11
|
-
|
|
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
12
|
proxy routing and deployment policy remain operator responsibilities. Redaction
|
|
13
13
|
and scoped authority do not isolate an unrestricted same-user program.
|
package/STORAGE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Persistence and recovery
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use targeted storage contracts. Node 24.12.0
|
|
4
4
|
or later is required. The optional `@poppykit/client/sqlite` entry point exports
|
|
5
5
|
`SqliteAuthStore` and `SqliteConversationStore`; importing the client core does not
|
|
6
6
|
load SQLite. `node:sqlite` is experimental in the tested Node 24.12.0 runtime.
|
|
@@ -44,8 +44,8 @@ records its endpoint. Retry rejects an endpoint change before sending. Preparati
|
|
|
44
44
|
returns the existing record instead of replacing content; terminal outcomes cannot
|
|
45
45
|
be overwritten by a late sender. Checkpoints include endpoint, conversation and
|
|
46
46
|
consumer. Receipts remain available for retries: the old `keepCompleted` deletion
|
|
47
|
-
behavior is removed.
|
|
48
|
-
|
|
47
|
+
behavior is removed. Retention preserves message identity and bounds payload cleanup; no automatic
|
|
48
|
+
age-based deletion is performed.
|
|
49
49
|
|
|
50
50
|
`MemoryConversationStore` from `@poppykit/client/memory` is non-durable and confined
|
|
51
51
|
to one process. The SQLite adapters also accept `:memory:` for non-durable tests;
|
|
@@ -84,11 +84,8 @@ aside its database and WAL/SHM files and the matching host-managed secret namesp
|
|
|
84
84
|
before creating a fresh store. Never reset live credentials by deleting a database
|
|
85
85
|
behind a running process. Old stores are not silently deleted or reinterpreted.
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
synthetic effect followed by owner death. The latter becomes uncertain, not an
|
|
90
|
-
exactly-once execution claim. Auth unit tests also retain staged-secret, generation,
|
|
91
|
-
revocation-race and vault-failure coverage.
|
|
87
|
+
Contract tests cover real child-process contention, crash recovery, uncertain
|
|
88
|
+
external effects, staged secrets and revocation races; see [contributing](CONTRIBUTING.md).
|
|
92
89
|
|
|
93
90
|
## Conversation delivery lifetime
|
|
94
91
|
|
|
@@ -97,3 +94,63 @@ checkpoints are retained until explicit local deletion; pending/uncertain retrie
|
|
|
97
94
|
must first be reconciled. Checkpoint acknowledgements use compare-and-set and do
|
|
98
95
|
not acknowledge merely because a page or stream frame was read. Local deletion
|
|
99
96
|
never deletes company history, revokes access or cancels accepted business work.
|
|
97
|
+
|
|
98
|
+
## Postgres (0.2 API)
|
|
99
|
+
|
|
100
|
+
The optional `@poppykit/client/postgres` exports `PostgresAuthStore` and
|
|
101
|
+
`PostgresConversationStore`; `/provider/postgres` exports `PostgresProviderStore`.
|
|
102
|
+
Install the optional peer `pg@8.23.1` only when choosing these subpaths.
|
|
103
|
+
Protocol-only imports load neither pg nor SQLite. PostgreSQL 17 and Node 24.12+
|
|
104
|
+
are supported; plain PostgreSQL and Supabase transaction pooling use the same adapter.
|
|
105
|
+
There is no Supabase Auth or application dependency.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { createPostgresPool, migratePostgres, PostgresAuthStore,
|
|
109
|
+
PostgresConversationStore } from '@poppykit/client/postgres';
|
|
110
|
+
import { PostgresProviderStore } from '@poppykit/client/provider/postgres';
|
|
111
|
+
const pool = createPostgresPool({ connectionString: process.env.DATABASE_URL });
|
|
112
|
+
// Explicit deployment administration, once before accepting traffic:
|
|
113
|
+
await migratePostgres(pool, 'poppy_client');
|
|
114
|
+
await migratePostgres(pool, 'poppy_provider');
|
|
115
|
+
const auth = new PostgresAuthStore(pool, 'installation:user');
|
|
116
|
+
const conversations = new PostgresConversationStore(pool, 'installation:user');
|
|
117
|
+
const provider = new PostgresProviderStore(pool, 'provider-instance');
|
|
118
|
+
// After requests finish:
|
|
119
|
+
await auth.close(); await conversations.close(); await provider.close();
|
|
120
|
+
await pool.end();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
A supplied pool belongs to the caller: adapter `close` stops that adapter and
|
|
124
|
+
never ends the pool. `createPostgresPool` returns a pool owned by its caller.
|
|
125
|
+
Defaults: two connections, 3s acquisition, 10s idle, verified TLS. Supply a CA via
|
|
126
|
+
`ssl: { ca, rejectUnauthorized: true }` for a private root. TLS URL overrides and
|
|
127
|
+
`rejectUnauthorized: false` are rejected; `ssl: false` is an explicit trusted-local
|
|
128
|
+
server choice. Caller-created pools are the caller's responsibility. Queries are
|
|
129
|
+
unnamed/parameterized, and each transaction uses one checked-out connection.
|
|
130
|
+
Transaction/statement limits are 5s and lock waits 2s. No session locks, named
|
|
131
|
+
prepared statements, LISTEN or session state are required by transaction pooling.
|
|
132
|
+
Do not perform HTTP, model or KMS calls in storage callbacks.
|
|
133
|
+
|
|
134
|
+
Versioned SQL ships at `migrations/postgres/0001_poppy_client.sql` and
|
|
135
|
+
`0001_poppy_provider.sql`. `postgresMigration(schema)` returns its SQL for review;
|
|
136
|
+
`migratePostgres` explicitly applies it atomically or accepts existing version 1.
|
|
137
|
+
Construction/request paths never create or upgrade schemas. Unknown/missing
|
|
138
|
+
versions fail closed. Version checks do not certify arbitrary operator edits to
|
|
139
|
+
table definitions: inspect deployment migration history and stop on schema drift.
|
|
140
|
+
Applied migration files are immutable; corrections require a new migration/version.
|
|
141
|
+
Keep SQL and the deployment's applied-migration ledger under version control.
|
|
142
|
+
Back up and rehearse before applying to durable data. Roll back application code
|
|
143
|
+
only to a compatible schema/API; restore a reviewed backup for destructive reversal.
|
|
144
|
+
No automatic SQLite import, live reset, schema drop or downgrade is supplied.
|
|
145
|
+
|
|
146
|
+
Auth uses its namespace owner/epoch fence and indexed connection/operation selectors.
|
|
147
|
+
Provider uses indexed enrollment/state/mapping selectors, an atomic capacity counter,
|
|
148
|
+
and bounded per-enrollment operations. These are individual row records, not a
|
|
149
|
+
whole-database snapshot. `cleanup` preserves pending/uncertain auth operations.
|
|
150
|
+
Namespaces are constructor configuration, never request-supplied authority.
|
|
151
|
+
|
|
152
|
+
Custom backends can run `runClientConversationAdapterContract` and
|
|
153
|
+
`runProviderAdapterContract` from `@poppykit/client/adapter-contract` with two
|
|
154
|
+
handles to a fresh shared namespace. Real process death and engine atomicity still
|
|
155
|
+
need backend-specific checks. Database credentials and encryption keys stay outside
|
|
156
|
+
records, prompts and logs; provider callback URLs remain encrypted by ResultCipher.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ConversationStore } from './conversation-store.js';
|
|
2
|
+
import type { ProviderStore } from './provider.js';
|
|
3
|
+
export type ClientConversationAdapterFixture = () => Promise<{
|
|
4
|
+
store: ConversationStore;
|
|
5
|
+
second: ConversationStore;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
}>;
|
|
8
|
+
export type ProviderAdapterFixture = () => Promise<{
|
|
9
|
+
store: ProviderStore;
|
|
10
|
+
second: ProviderStore;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}>;
|
|
13
|
+
/** Run against two handles sharing a fresh namespace; real engine/process tests remain the host's responsibility. */
|
|
14
|
+
export declare function runClientConversationAdapterContract(fixture: ClientConversationAdapterFixture): void;
|
|
15
|
+
export declare function runProviderAdapterContract(fixture: ProviderAdapterFixture): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
/** Run against two handles sharing a fresh namespace; real engine/process tests remain the host's responsibility. */
|
|
4
|
+
export function runClientConversationAdapterContract(fixture) {
|
|
5
|
+
test('client adapter contract: atomic receipt preparation, terminal settlement and checkpoint compare-and-swap', async () => {
|
|
6
|
+
const f = await fixture();
|
|
7
|
+
try {
|
|
8
|
+
const key = { connectionId: 'contract', issuer: 'https://issuer.example/one', endpoint: 'https://company.example/conversations' };
|
|
9
|
+
const row = { ...key, id: 'message', fingerprint: 'original', phase: 'pending', target: null, request: { message: { id: 'message', sender: 'agent', text: 'hello' } } };
|
|
10
|
+
const saved = await Promise.all([f.store.prepareOutbound(key, row), f.second.prepareOutbound(key, { ...row, fingerprint: 'changed' })]);
|
|
11
|
+
assert.equal(saved[0].fingerprint, saved[1].fingerprint);
|
|
12
|
+
saved[0].request.message.text = 'mutated';
|
|
13
|
+
assert.equal((await f.second.getOutbound(key, row.id))?.request.message.text, 'hello');
|
|
14
|
+
await f.store.settleOutbound(key, row.id, { phase: 'accepted', response: { conversation_id: 'conversation', status: 'idle', responder: 'agent' } });
|
|
15
|
+
await f.second.settleOutbound(key, row.id, { phase: 'rejected', error: { code: 'invalid_request', status: 400 } });
|
|
16
|
+
assert.equal((await f.store.getOutbound(key, row.id))?.phase, 'accepted');
|
|
17
|
+
await assert.rejects(f.store.settleOutbound({ ...key, endpoint: 'https://other.example/conversations' }, row.id, { phase: 'accepted' }));
|
|
18
|
+
assert.equal(await f.store.getOutbound({ ...key, issuer: 'https://issuer.example/two' }, row.id), undefined);
|
|
19
|
+
const checkpoint = { connectionId: key.connectionId, issuer: key.issuer, conversationId: 'conversation', name: 'consumer', cursor: 'one' };
|
|
20
|
+
await Promise.all([f.store.advanceCheckpoint(key, checkpoint, [undefined]), f.second.advanceCheckpoint(key, { ...checkpoint, cursor: 'two' }, [undefined])]);
|
|
21
|
+
const current = await f.store.getCheckpoint(key, 'conversation', 'consumer');
|
|
22
|
+
assert.ok(current === 'one' || current === 'two');
|
|
23
|
+
await f.second.advanceCheckpoint(key, { ...checkpoint, cursor: 'stale' }, [undefined]);
|
|
24
|
+
assert.equal(await f.store.getCheckpoint(key, 'conversation', 'consumer'), current);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await f.close();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function runProviderAdapterContract(fixture) {
|
|
32
|
+
test('provider adapter contract: capacity races, detached results, rollback and selection boundaries', async () => {
|
|
33
|
+
const f = await fixture(), selection = { kind: 'enroll', subjectHash: 'subject', requestHash: 'request' };
|
|
34
|
+
try {
|
|
35
|
+
await Promise.all(Array.from({ length: 10 }, (_, i) => (i % 2 ? f.store : f.second).transaction(selection, tx => { tx.put('capacity', 'enrollments', { count: (tx.get('capacity', 'enrollments')?.count ?? 0) + 1 }); })));
|
|
36
|
+
const result = await f.store.transaction(selection, tx => tx.get('capacity', 'enrollments'));
|
|
37
|
+
assert.equal(result?.count, 10);
|
|
38
|
+
result.count = 999;
|
|
39
|
+
await assert.rejects(f.store.transaction(selection, tx => { tx.put('capacity', 'enrollments', { count: 0 }); throw new Error('rollback'); }), /rollback/);
|
|
40
|
+
await assert.rejects(f.store.transaction(selection, async (tx) => { tx.put('capacity', 'enrollments', { count: 0 }); }), /synchronous/);
|
|
41
|
+
await assert.rejects(f.store.transaction(selection, tx => tx.get('enrollment', 'foreign')), /outside selection/);
|
|
42
|
+
assert.equal((await f.second.transaction(selection, tx => tx.get('capacity', 'enrollments')))?.count, 10);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await f.close();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Pool, PoolConfig, QueryResult, QueryResultRow } from 'pg';
|
|
2
|
+
export type PostgresSchema = 'poppy_client' | 'poppy_provider' | 'poppy_server';
|
|
3
|
+
export type PostgresConnection = {
|
|
4
|
+
query<R extends QueryResultRow = QueryResultRow>(sql: string, values?: unknown[]): Promise<QueryResult<R>>;
|
|
5
|
+
};
|
|
6
|
+
export declare function json(value: unknown): string;
|
|
7
|
+
export declare function synchronous<T>(value: T): T;
|
|
8
|
+
/** Remote defaults verify TLS; plaintext must be explicitly selected for a trusted local test server. */
|
|
9
|
+
export declare function createPostgresPool(config: PoolConfig): Pool;
|
|
10
|
+
export declare const postgresSchemaVersion = 1;
|
|
11
|
+
export declare function postgresMigration(schema: PostgresSchema): Promise<string>;
|
|
12
|
+
/** Explicit administration only. Constructors never execute DDL. */
|
|
13
|
+
export declare function migratePostgres(pool: Pool, schema: PostgresSchema): Promise<void>;
|
|
14
|
+
export declare class PostgresRuntime {
|
|
15
|
+
readonly pool: Pool;
|
|
16
|
+
readonly schema: PostgresSchema;
|
|
17
|
+
private readonly ownsPool;
|
|
18
|
+
private readonly context;
|
|
19
|
+
private closed;
|
|
20
|
+
readonly metrics: {
|
|
21
|
+
rowsRead: number;
|
|
22
|
+
rowsWritten: number;
|
|
23
|
+
};
|
|
24
|
+
constructor(pool: Pool, schema: PostgresSchema, ownsPool?: boolean);
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
run<T>(action: () => Promise<T>): Promise<T>;
|
|
27
|
+
connection(): PostgresConnection;
|
|
28
|
+
query<R extends QueryResultRow = QueryResultRow>(sql: string, values?: unknown[]): Promise<R[]>;
|
|
29
|
+
lock(key: string): Promise<void>;
|
|
30
|
+
}
|