@jskit-ai/agent-docs 0.1.193 → 0.1.194

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 (28) hide show
  1. package/guide/agent/app-setup/database-layer.md +74 -5
  2. package/guide/agent/app-setup/existing-application-migration.md +34 -29
  3. package/guide/agent/app-setup/multi-homing.md +93 -116
  4. package/guide/agent/framework/crud-operations.md +16 -2
  5. package/package.json +1 -1
  6. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  7. package/patterns/minimal-foundation/example/package.json +4 -4
  8. package/patterns/shell-foundation/example/package.json +5 -5
  9. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  10. package/reference/autogen/PATTERN_INDEX.md +35 -35
  11. package/skills/jskit/SKILL.md +1 -1
  12. package/skills/jskit/references/crud-operations.md +16 -2
  13. package/skills/jskit/references/existing-application-migration.md +34 -29
  14. package/skills/jskit/references/multi-homing.md +93 -116
  15. package/skills/jskit/references/pattern-index.md +35 -35
  16. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  17. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  18. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  19. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  20. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  21. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  22. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  23. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  24. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  25. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  26. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  27. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  28. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +23 -28
@@ -111,8 +111,67 @@ package's metadata:
111
111
  }
112
112
  ```
113
113
 
114
- Names must remain unique across the effective migration directories. Keep
115
- constraints in a later migration when ordering matters.
114
+ Names must remain unique across the effective migration directories. Preserve
115
+ the basename when relocating a migration: Knex records that name in its ledger.
116
+ Do not rename it to force execution or edit an already-applied migration's body;
117
+ changed schema behavior requires a new migration.
118
+
119
+ ### Directory order comes before filenames
120
+
121
+ `createKnexMigrationConfigFromApp()` sets `sortDirsSeparately: true` and runs
122
+ pending migrations in this order, using the default `migrationsDirectory`:
123
+
124
+ 1. Application `migrations/`.
125
+ 2. Discovered package migration directories, sorted by their absolute paths
126
+ using `localeCompare`.
127
+ 3. Application `migrations/constraints/`.
128
+
129
+ Knex sorts filenames within each directory, not across the whole graph; see
130
+ its [`sortDirsSeparately` option](https://knexjs.org/guide/migrations.html#migration-api).
131
+ Package discovery does not topologically order tables or foreign keys. A newer
132
+ timestamp in an earlier package still runs before every file in a later package.
133
+ Inspect the effective `knexfile.js` configuration if the app customizes it.
134
+
135
+ Keep table and column creation with the owning package. Put cross-package
136
+ foreign keys and other constraints that need the complete package schema in
137
+ application `migrations/constraints/`. An ordinary root `migrations/` file runs
138
+ too early for this purpose. For example, a `pet_notes.product_id` column can be
139
+ added by the pet-notes package, but its foreign key to the products package's
140
+ `products.id` belongs in the final constraints phase. Do not rely on today's
141
+ package-directory order to make a cross-package reference work.
142
+
143
+ Before adding the key, check that both tables and columns exist, the referenced
144
+ key is indexed appropriately, column types match (including integer signedness),
145
+ existing values have valid parents, and `ON DELETE SET NULL` uses a nullable
146
+ column. An incorrectly formed foreign key is not always an ordering failure.
147
+ Do not suppress the failure, skip a missing parent, or disable foreign-key
148
+ checks to make preparation appear successful.
149
+
150
+ ### MySQL and MariaDB failures can leave partial schema
151
+
152
+ MySQL and MariaDB DDL can commit implicitly. If adding a column succeeds and a
153
+ later statement adding its foreign key fails, the column can remain even though
154
+ Knex has not recorded that migration as completed. A Knex transaction does not
155
+ make a multi-statement DDL migration atomic. See the
156
+ [MySQL implicit-commit rules](https://dev.mysql.com/doc/refman/8.4/en/implicit-commit.html)
157
+ and [MariaDB implicit-commit rules](https://mariadb.com/docs/server/reference/sql-statements/transactions/sql-statements-that-cause-an-implicit-commit).
158
+
159
+ Design new multi-step DDL migrations for their concrete partial states. Inspect
160
+ the column and foreign key independently; `hasColumn()` alone cannot prove the
161
+ constraint exists. Verify the definition of an existing object before skipping
162
+ its creation, complete only missing work, and fail clearly on incompatible
163
+ schema. Do not catch arbitrary SQL errors as success or mark incomplete work
164
+ as applied. Keep recovery local to the affected migration or an explicit repair;
165
+ do not add schema repair to request handlers or normal server startup.
166
+
167
+ For an existing failure, inspect the ledger and actual schema read-only first.
168
+ Preserve applied migration bodies and use a new corrective migration for
169
+ databases that already recorded them. If an earlier pending migration prevents
170
+ that correction from running, describe the required backed-up, explicit repair
171
+ before retrying; a later file alone cannot repair a blocked earlier step. Any
172
+ relocation must retain its basename and be checked against both fresh and
173
+ existing ledgers. Never drop valuable data or delete ledger entries to force
174
+ a replay.
116
175
 
117
176
  ## Seed data is not a migration
118
177
 
@@ -151,12 +210,22 @@ not merely because a useful resource has behavior beyond list and save.
151
210
 
152
211
  ## Verification
153
212
 
154
- - Rebuild a disposable database from the complete migration graph.
155
- - Run `npm run db:migrate:status` after migration.
213
+ - For every schema change, run the normal preparation entrypoint against a
214
+ fresh disposable database on the selected engine with the complete package
215
+ graph. An existing development database can hide ordering failures.
216
+ - In a separate disposable fixture at the previous schema and ledger, apply
217
+ the upgrade and prove representative existing rows survive.
218
+ - For multi-step MySQL or MariaDB DDL, reproduce partial completion (for
219
+ example, column present, foreign key absent, migration still pending), retry
220
+ preparation, and verify the final column and constraint definitions.
221
+ - Run preparation again and `npm run db:migrate:status`; require no pending
222
+ migrations. Prove invalid references are rejected and the declared delete
223
+ behavior works. A stubbed Knex test or successful process exit is insufficient.
156
224
  - Exercise a real transaction and one invalid-connection case.
157
225
  - When a seed operation exists, run it twice and require the second run to be
158
226
  safe.
159
- - Test MySQL and PostgreSQL patterns independently.
227
+ - Use the application's selected engine; exercise each supported engine when
228
+ changing a shared pattern. Report unavailable database verification explicitly.
160
229
 
161
230
  Do not add migration receipts, sync ledgers, generator provenance, or dialect
162
231
  questionnaires. The installed graph, migration source, environment, and
@@ -224,33 +224,28 @@ or unresolved write.
224
224
 
225
225
  ### Workspace invitation transaction compatibility (workspaces-core 0.1.198)
226
226
 
227
- Workspace invitation creation, acceptance and refusal now own managed
228
- transactions. Existing calls to `members.createInvite(..., { trx })` must become
229
- `members.prepareInvite(..., { trx })`, application association/receipt writes,
230
- confirmed commit, then `members.sendInvite(...)` without `trx`. Move writes that
231
- must accompany built-in acceptance into
232
- `pendingInvitations.registerAcceptanceParticipant(fn)` during feature setup.
233
- Acceptance/refusal reject borrowed transactions; revocation can still participate
234
- in a managed transaction. Ordinary invitation actions retain their IDs and
235
- response shapes. Terminal repository transitions now reject instead of
236
- rewriting already accepted/revoked invitations. Mailer exceptions return a fixed
237
- message to avoid leaking invitation tokens from provider diagnostics.
238
-
239
- Required participant writes roll back together. Preserve transaction outcomes:
240
- a failure after commit is not rollback, and an unresolved commit must not be
241
- blindly replayed. Matching-recipient expiry revocation deliberately commits
242
- before its 409 rejection. Review the
243
- [workspace invitation contract and example](./multi-homing.md#transactional-invitation-participation)
244
- when migrating. No schema migration is required for these framework APIs.
245
-
246
- The first coordinated release containing this contract includes
227
+ Invitation creation, acceptance and refusal now own their managed transactions:
228
+
229
+ - Replace `members.createInvite(..., { trx })` with
230
+ `members.prepareInvite(..., { trx })`, save the application association/receipt,
231
+ commit, then call `members.sendInvite(...)` without `trx`.
232
+ - Move related acceptance writes into
233
+ `pendingInvitations.registerAcceptanceParticipant(fn)` during feature setup.
234
+ Acceptance/refusal reject borrowed transactions; revocation can still join one.
235
+ - Terminal repository transitions reject accepted/revoked invitations. Mailer
236
+ exceptions return a fixed message to keep tokens out of provider diagnostics.
237
+
238
+ Participant writes roll back together. Preserve transaction outcomes when handling
239
+ errors: completion-hook failures cannot undo a commit, and expiry revocation
240
+ commits before returning 409. See the
241
+ [workspace invitation contract and example](./multi-homing.md#transactional-invitation-participation).
242
+ Action IDs, response shapes and schema are unchanged.
243
+
244
+ The first coordinated release includes
247
245
  `@jskit-ai/workspaces-core@0.1.198`, `@jskit-ai/jskit-catalog@0.1.245` and
248
- `@jskit-ai/agent-docs@0.1.192`. Once that release is published, use
249
- `npm run jskit:update` in the application to upgrade the coordinated graph,
250
- restart the backend, check the actual installed versions, and assert
251
- that `prepareInvite`, `sendInvite` and `registerAcceptanceParticipant` are present
252
- on the resolved capability. Application domain integration and real email
253
- activation remain separate work.
246
+ `@jskit-ai/agent-docs@0.1.192`. Run `npm run jskit:update`, restart the backend,
247
+ and check the installed versions and methods on `workspaces.core`. Application
248
+ integration and real email activation remain separate work.
254
249
 
255
250
  ## 4. Adopt migrations without breaking either database history
256
251
 
@@ -261,9 +256,15 @@ historical migration when moving it so the existing Knex ledger continues to
261
256
  recognize it. Basenames must be unique across the effective graph.
262
257
 
263
258
  Keep cross-package foreign keys and other dependency-sensitive constraints in
264
- later application-owned migrations when package ordering requires it. Seeds
265
- are not migrations: run one explicit application-owned, idempotent seed after
266
- the complete graph has migrated.
259
+ application `migrations/constraints/`, the final phase of
260
+ `createKnexMigrationConfigFromApp()`. It uses `sortDirsSeparately: true`: root
261
+ `migrations/` runs before the sorted package directories, and filenames only
262
+ order work within their own directory. A later timestamp in a package does not
263
+ defer its foreign key until another package has created the parent table.
264
+ Follow the [database ordering and partial-failure recovery rules](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/database-layer#directory-order-comes-before-filenames),
265
+ including independent checks for columns and constraints after MySQL or MariaDB
266
+ DDL fails. Seeds are not migrations: run one explicit application-owned,
267
+ idempotent seed after the complete graph has migrated.
267
268
 
268
269
  Prove both histories:
269
270
 
@@ -274,6 +275,10 @@ Prove both histories:
274
275
  with the intended schema.
275
276
  3. Run the normal migrate-then-seed entry point twice. The second run must have
276
277
  no pending migration and the seed must be safe and stable.
278
+ 4. In disposable fixtures, prove upgrade from the previous schema preserves
279
+ rows, and multi-step MySQL or MariaDB DDL resumes from partial completion
280
+ with the intended foreign keys enforced. Read-only ledger inspection alone
281
+ does not prove an upgrade or recovery.
277
282
 
278
283
  A managed editor allocates one isolated mutable development database per
279
284
  session. It owns creation, credentials, lifetime, and environment injection;
@@ -41,23 +41,20 @@ a policy/runtime mismatch.
41
41
 
42
42
  ## Transactional invitation participation
43
43
 
44
- Use the existing `workspaces.core` capability when accepting an invitation must
45
- also connect an application-owned identity, training history, access scopes or
46
- required audit records. JSKIT owns tokens, invitation state and membership; the
47
- application owns its reviewed association and domain policy. The built-in
48
- `workspace.invite.redeem` action runs the participant. No replacement route,
49
- client follow-up save or after-action event is needed.
44
+ Use `workspaces.core` to connect application records during the built-in
45
+ `workspace.invite.redeem` action. JSKIT owns tokens, invitation state and
46
+ membership. The application owns its reviewed person association, training,
47
+ access scopes and audit records.
50
48
 
51
- Saving intended access without sending remains an application draft. Do not call
52
- preparation until an administrator deliberately issues an invitation: preparation
53
- creates a real token and pending invitation, but does not activate membership.
49
+ Keep intended access in an application draft until an administrator issues an
50
+ invitation. Preparation creates a token and pending invitation without activating
51
+ membership or sending mail.
54
52
 
55
53
  ### Public server methods
56
54
 
57
- These methods are trusted server composition APIs, not new public actions. The
58
- caller must authorize creation, delivery and revocation in the requested workspace.
59
- The ordinary invitation routes retain their existing authorization and role
60
- validation. Do not forward arbitrary HTTP input as server composition options.
55
+ These server APIs require the caller to authorize creation, delivery and
56
+ revocation in the requested workspace. Ordinary invitation routes retain their
57
+ authorization and role checks. Do not pass arbitrary HTTP input as options.
61
58
 
62
59
  | Capability method | Contract |
63
60
  | --- | --- |
@@ -70,20 +67,18 @@ validation. Do not forward arbitrary HTTP input as server composition options.
70
67
 
71
68
  `sendInvite` accepts `{ createdInviteId, inviteTokenPreview }` from preparation or
72
69
  only `{ createdInviteId }` for a later attempt. The latter uses JSKIT's existing
73
- opaque token-hash encoding without rotating the token. Supplying an empty or
74
- mismatched token rejects. Email, role, expiry and workspace display fields come
75
- from the stored invitation/workspace, not replacement input. Its response keeps
70
+ opaque token-hash encoding without rotating the token. Empty or mismatched tokens
71
+ reject. Email, role, expiry and workspace display fields come from stored records.
72
+ The response includes
76
73
  `createdInviteId`, `inviteTokenPreview`, `inviteUrl` and `inviteDelivery` alongside
77
- the ordinary workspace/invitation-list payload. Treat tokens and URLs as secrets;
78
- do not log them or put them in diagnostic messages or administrative receipts.
79
- A receipt normally needs only the exact invitation ID.
74
+ the ordinary workspace/invitation-list payload. Keep tokens and URLs out of logs,
75
+ diagnostics and administrative receipts; save the invitation ID in the receipt.
80
76
 
81
77
  ### Preparation, association, commit and controlled delivery
82
78
 
83
- Inside application feature setup, declare `workspaces: "workspaces.core"` in
84
- `requires`, then use that capability. The `reviewedPeople`, `people`, `scopes`
85
- and `audit` names below represent application-owned services, not JSKIT APIs.
86
- Their writes must forward the provided transaction to their repositories.
79
+ Declare `workspaces: "workspaces.core"` in the application's feature `requires`.
80
+ In this example, `reviewedPeople`, `people`, `scopes` and `audit` are application
81
+ services. Each forwards the supplied transaction to its repositories.
87
82
 
88
83
  ```js
89
84
  // During application Feature.setup, before serving requests:
@@ -118,25 +113,22 @@ const prepared = await invites.withTransaction(async (trx) => {
118
113
  }, options);
119
114
  await reviewedPeople.associateInvitationAndSaveReceipt(reviewed, requestId, created.createdInviteId, options);
120
115
  return { createdInviteId: created.createdInviteId };
121
- }); // Confirmed commit; a rejection here must not fall through to delivery.
116
+ });
122
117
 
123
118
  const result = await members.sendInvite(workspace, actor, prepared, { context });
124
- // Persist delivery feedback through the application owner. Avoid logging result:
125
- // it contains a secret token and URL as well as the nonsecret delivery outcome.
119
+ // Store the delivery outcome, not the token or URL in result.
126
120
  await reviewedPeople.recordDeliveryOutcome(prepared.createdInviteId, result.inviteDelivery, { context });
127
121
  ```
128
122
 
129
- The application must lock its reviewed draft/receipt, enforce request identity,
130
- and reject changed input on a reused request ID. A retry after losing the
131
- preparation response reads that receipt instead of preparing another invitation.
132
- Association failure rolls back the new invitation, replacement of an older
133
- invitation, receipt and application writes. This is not generic framework
134
- idempotency, and does not imply exactly-once email.
123
+ Lock the reviewed draft/receipt and reject changed input on a reused request ID.
124
+ After a lost preparation response, read the receipt instead of preparing another
125
+ invitation. Association failure rolls back the new invitation, replacement of
126
+ the previous invitation, receipt and application writes. Send only after the
127
+ transaction resolves successfully.
135
128
 
136
- Real SMTP is unnecessary for implementation and verification. With no
137
- `workspaceInviteMailer`, delivery returns `status: "mailer_unconfigured"`, an
138
- explanatory `message` and an empty `providerMessageId`; the invitation remains
139
- committed. A test application can configure a controlled mailer:
129
+ With no `workspaceInviteMailer`, delivery returns `status: "mailer_unconfigured"`,
130
+ an explanatory `message` and an empty `providerMessageId`; the invitation remains
131
+ committed. Use a controlled mailer in tests; SMTP is unnecessary:
140
132
 
141
133
  ```js
142
134
  workspaceInviteMailer: {
@@ -147,86 +139,75 @@ workspaceInviteMailer: {
147
139
  }
148
140
  ```
149
141
 
150
- A thrown renderer/mailer error returns `inviteDelivery.status: "failed"` and a
151
- fixed message, avoiding accidental token leakage through provider exceptions.
152
- It does not delete the invitation/association or activate membership. Mailer
153
- results retain their normalized status, message and provider message ID; a
154
- successful default is `sent`. A provider exception or interrupted response may
155
- occur after a provider accepted the message: `failed` is not proof of nondelivery.
156
- There is no exactly-once delivery guarantee. Applications own durable delivery
157
- attempts and safe retry policy. Validation checks committed invitation state
158
- before sending; revocation after sending starts cannot recall the message.
142
+ Renderer/mailer exceptions return `inviteDelivery.status: "failed"` with a fixed
143
+ message to keep provider errors from exposing tokens. Persistence is unchanged.
144
+ Successful mailer results retain their normalized status, message and provider
145
+ message ID; the default status is `sent`.
146
+
147
+ A provider may accept a message before throwing or losing its response. The
148
+ application must track delivery attempts and decide when to retry; email is not
149
+ exactly once. Revocation after sending starts cannot recall a message.
159
150
 
160
151
  ### Acceptance and failure semantics
161
152
 
162
- Register one callback during feature setup. It composes any application domain
163
- owners explicitly. Non-functions throw `TypeError`; a second registration
164
- throws, and registration closes when acceptance/refusal first begins. The
165
- callback's invite, user and options envelope are shallow frozen. It receives the
166
- same managed `trx` and request `context`; transaction ownership stays with JSKIT.
167
-
168
- Acceptance authenticates the user, checks email, pending state and expiry, then
169
- revalidates the invitation under workspace/invitation locks. Membership is
170
- available as active inside that transaction before the callback runs. Existing
171
- role-upsert semantics remain: the stored invitation role becomes the membership
172
- role, including for an existing membership. The callback cannot authorize role
173
- or scope overrides from client input. It must recheck the reviewed version,
174
- workspace, current inviter authority, person eligibility, existing account link,
175
- and scope eligibility using application-owned data.
176
-
177
- An ordinary invitation with no application association may pass through. A
178
- stale, revoked, conflicting or expected-but-missing association must fail closed.
179
- The application needs enough durable association/review state to distinguish
180
- these cases; absence alone cannot identify a deleted reviewed association.
181
-
182
- Await every owner write and forward the same transaction. Do not commit,
183
- roll back, use savepoints, start a new transaction, or perform external side
184
- effects in the callback. Do not swallow failures. Participant exceptions and
185
- later invitation-status failures roll back membership (including its prior role
186
- and status), person/training links, scopes, required audit and accepted status.
187
- The callback is not invoked for an invalid invitation or on refusal. Acceptance
188
- and refusal reject borrowed `options.trx`; revocation can join a managed
189
- transaction or own one when none is supplied.
190
-
191
- A missing/already-handled invitation rejects with 404; mismatched authenticated
192
- email with 403; unauthenticated access with 401. An expired invitation for its
193
- matching recipient is revoked in a committed transaction and then rejects with
194
- 409 and `transactionOutcome: "committed"`. It never activates membership or
195
- runs the participant. A failure to commit that revocation preserves the managed
196
- transaction error instead. Delivery rejects missing/foreign/handled invitations
197
- with 404, expired invitations with 409, and mismatched tokens with 400.
198
-
199
- Keep `error.transactionOutcome` and `error.cause` when translating failures.
200
- `rolledBack` confirms rollback; `committed` can indicate a completion-hook error
201
- after successful persistence; `unknown` leaves the outcome unresolved. A thrown
202
- error alone does not prove rollback. After-commit failures and external effects
203
- cannot be included in the atomic guarantee. See
153
+ Register one callback during feature setup to call the required application
154
+ services. Non-functions throw `TypeError`; duplicate registration throws, and
155
+ registration closes when acceptance/refusal first begins. The callback's invite,
156
+ user and options are shallow frozen. JSKIT owns the transaction and passes its
157
+ `trx` and request `context` to the callback.
158
+
159
+ Acceptance checks authentication, recipient email, pending state and expiry under
160
+ workspace/invitation locks. Before calling the participant, it activates membership
161
+ with the stored invitation role, including when updating an existing membership.
162
+ The participant must use application data to check the review version, workspace,
163
+ current inviter authority, person eligibility, existing account link and scope
164
+ eligibility. Client input cannot authorize role or scope overrides.
165
+
166
+ An ordinary invitation without an application association may pass through.
167
+ Reject stale, revoked, conflicting or unexpectedly missing associations. Retain
168
+ enough review history to distinguish ordinary invitations from deleted associations.
169
+
170
+ Await every write with the same transaction and let failures propagate. The
171
+ callback must not commit, roll back, create transactions/savepoints or perform
172
+ external effects. Participant and later invitation-status failures roll back
173
+ membership (including its previous role/status), person/training links, scopes,
174
+ audit and accepted status. Invalid invitations and refusals do not call the
175
+ participant. Acceptance/refusal reject borrowed `options.trx`; revocation can
176
+ join a managed transaction or create one.
177
+
178
+ Acceptance/refusal return 401 for unauthenticated access, 403 for a recipient
179
+ email mismatch and 404 for a missing or handled invitation. A matching recipient's
180
+ expired invitation is revoked and committed before returning 409 with
181
+ `transactionOutcome: "committed"`, without activating membership or calling the
182
+ participant. A failed revocation commit retains its transaction error. Delivery
183
+ returns 404 for missing, foreign or handled invitations, 409 for expiry and 400
184
+ for a token mismatch.
185
+
186
+ Preserve `error.transactionOutcome` and `error.cause`: `rolledBack` confirms
187
+ rollback, `committed` can accompany a failed completion hook, and `unknown`
188
+ requires resolving the outcome before retrying. Errors after commit cannot undo
189
+ persistence. See
204
190
  [managed transaction guidance](./existing-application-migration.md#json-rest-v2-integration).
205
191
 
206
- Invitation mutations lock workspace before invitation rows, and use current
207
- locking reads before terminal transitions. Accepted/revoked state cannot be
208
- overwritten by a losing acceptance, refusal, revocation or replacement. A
209
- replacement after acceptance may create a new pending invitation while retaining
210
- the older accepted invitation. Keep callbacks short: invitation changes in the
211
- same workspace serialize. Database deadlocks/serialization failures remain
212
- transaction errors; callbacks are not automatically replayed.
192
+ Invitation mutations lock workspace before invitation rows. A losing acceptance,
193
+ refusal, revocation or replacement cannot overwrite accepted/revoked state.
194
+ Replacement can create a new pending invitation after acceptance while retaining
195
+ the accepted invitation. Keep callbacks short: invitation changes in one workspace
196
+ serialize. Deadlocks/serialization failures propagate without replaying callbacks.
213
197
 
214
198
  ### Compatibility and verification
215
199
 
216
- This server-composition contract starts with `@jskit-ai/workspaces-core@0.1.198`.
217
- Transactional callers of
218
- `createInvite` must migrate to preparation plus post-commit delivery. Related
219
- acceptance writes must move into the participant. `markAcceptedById` and
220
- `revokeById` now reject terminal/missing invitations rather than overwriting their
221
- state. No route/action ID, package export, table or migration is added.
222
-
223
- Verify ordinary invitations, rollback after each domain write, late framework
224
- failure, existing membership restoration, recipient checks, stale associations,
225
- controlled delivery failures and lost-response receipts. Run the normal redeem
226
- action in tests. Use independent connections to race acceptance against acceptance,
227
- refusal, revocation and replacement in both winning orders. The package fixture
228
- runs these races on native MySQL/MariaDB and PostgreSQL; SQLite skips those races
229
- and is not evidence of native locking correctness.
200
+ Since `@jskit-ai/workspaces-core@0.1.198`, transactional `createInvite` callers
201
+ must use preparation followed by delivery after commit. Move related acceptance
202
+ writes into the participant. `markAcceptedById` and `revokeById` reject missing
203
+ or handled invitations. Route/action IDs, package exports and schema are unchanged.
204
+
205
+ Test through the normal redeem action: ordinary invitations, rollback after
206
+ application and final status writes, existing membership restoration, invalid
207
+ recipients/associations, controlled delivery failures and lost-response receipts.
208
+ The package fixture tests competing acceptance, refusal, revocation and replacement
209
+ in both winning orders on independent MySQL/MariaDB and PostgreSQL connections.
210
+ SQLite skips those races.
230
211
 
231
212
  Use only a verified empty disposable database named
232
213
  `jskit_repository_test_*` for native tests. The runner verifies the actual target,
@@ -239,17 +220,13 @@ node --test packages/workspaces-core/test/repositories.integration.test.js
239
220
  npm run verify
240
221
  ```
241
222
 
242
- A passing framework fixture does not complete the application's People, training,
243
- permissions, privacy or browser integration. Publication remains a separate
244
- maintainer operation. After a coordinated release is published, run the
245
- application's `npm run jskit:update`, restart its backend and verify that
223
+ Run the application's `npm run jskit:update`, restart its backend and verify that
246
224
  `members.prepareInvite`, `members.sendInvite` and
247
225
  `pendingInvitations.registerAcceptanceParticipant` are functions on the installed
248
- `workspaces.core` capability. Check the installed package/catalogue versions
249
- against that release. The first coordinated release containing this contract pairs
226
+ `workspaces.core` capability. The first release containing this contract pairs
250
227
  `@jskit-ai/workspaces-core@0.1.198`, `@jskit-ai/jskit-catalog@0.1.245` and
251
- `@jskit-ai/agent-docs@0.1.192`. Upgrade the coordinated dependency graph rather
252
- than pinning only `workspaces-core`. Do not patch installed `node_modules`.
228
+ `@jskit-ai/agent-docs@0.1.192`. Upgrade the coordinated dependency graph, then run
229
+ the application's identity, training, permission, privacy and browser tests.
253
230
 
254
231
  ## Browser composition
255
232
 
@@ -75,7 +75,21 @@ Migrations are immutable application source owned with their resource. Never
75
75
  make a live table or a generator the sole source of truth. Inspect schemas for
76
76
  adoption and diagnosis; inspection is optional.
77
77
 
78
- Before sign-off, rebuild in a fresh disposable database, compare schemas,
79
- test ownership boundaries and failures, and run current-state verification.
78
+ Read [ordering and recovery](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/database-layer#directory-order-comes-before-filenames)
79
+ before schema work; check Knex configuration and ledger.
80
+ `createKnexMigrationConfigFromApp()` uses `sortDirsSeparately: true`: root
81
+ `migrations/`, sorted package directories, then `migrations/constraints/`.
82
+ Timestamps order only within directories. Keep tables/columns with their package;
83
+ put cross-package foreign keys in the final phase.
84
+
85
+ MySQL/MariaDB can leave committed DDL without a ledger entry. Check columns and
86
+ keys separately; `hasColumn()` is insufficient. Validate existing definitions,
87
+ complete missing work, and plan recovery for blocked steps. Preserve applied
88
+ bodies and relocated basenames. Never disable foreign-key checks or skip parents.
89
+
90
+ On the selected engine, prove fresh preparation, upgrades preserving rows,
91
+ partial-DDL retries, and a second preparation with no pending migrations.
92
+ Inspect constraints and test rejected references and delete behavior.
93
+
80
94
  Do not create a workboard entry, ownership receipt, generation record, or
81
95
  historical proof that tooling ran.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.193",
3
+ "version": "0.1.194",
4
4
  "description": "Distributed JSKIT agent references, prompts, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -5,7 +5,7 @@
5
5
  "private": true,
6
6
  "type": "module",
7
7
  "dependencies": {
8
- "@jskit-ai/kernel": "0.1.221",
8
+ "@jskit-ai/kernel": "0.1.222",
9
9
  "json-rest-schema": "^1.0.17"
10
10
  },
11
11
  "exports": {
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.1.221",
37
+ "@jskit-ai/kernel": "0.1.222",
38
38
  "@tanstack/vue-query": "^5.101.0",
39
39
  "fastify": "^5.8.5",
40
40
  "json-rest-schema": "^1.0.17",
@@ -42,13 +42,13 @@
42
42
  "vue": "^3.5.43",
43
43
  "vue-router": "^5.3.1",
44
44
  "vuetify": "^4.1.2",
45
- "@jskit-ai/http-runtime": "0.1.219",
45
+ "@jskit-ai/http-runtime": "0.1.220",
46
46
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
47
47
  "@vue/devtools-api": "^8.2.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@jskit-ai/config-eslint": "0.1.218",
51
- "@jskit-ai/jskit-catalog": "0.1.246",
50
+ "@jskit-ai/config-eslint": "0.1.219",
51
+ "@jskit-ai/jskit-catalog": "0.1.247",
52
52
  "@playwright/test": "1.61.1",
53
53
  "@vitejs/plugin-vue": "^6.0.7",
54
54
  "eslint": "^10.8.0",
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.1.221",
37
+ "@jskit-ai/kernel": "0.1.222",
38
38
  "@tanstack/vue-query": "^5.101.0",
39
39
  "fastify": "^5.8.5",
40
40
  "json-rest-schema": "^1.0.17",
@@ -42,15 +42,15 @@
42
42
  "vue": "^3.5.43",
43
43
  "vue-router": "^5.3.1",
44
44
  "vuetify": "^4.1.2",
45
- "@jskit-ai/http-runtime": "0.1.219",
45
+ "@jskit-ai/http-runtime": "0.1.220",
46
46
  "@mdi/js": "^7.4.47",
47
- "@jskit-ai/shell-web": "0.1.225",
47
+ "@jskit-ai/shell-web": "0.1.226",
48
48
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
49
49
  "@vue/devtools-api": "^8.2.1"
50
50
  },
51
51
  "devDependencies": {
52
- "@jskit-ai/config-eslint": "0.1.218",
53
- "@jskit-ai/jskit-catalog": "0.1.246",
52
+ "@jskit-ai/config-eslint": "0.1.219",
53
+ "@jskit-ai/jskit-catalog": "0.1.247",
54
54
  "@playwright/test": "1.61.1",
55
55
  "@vitejs/plugin-vue": "^6.0.7",
56
56
  "eslint": "^10.8.0",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "description": "App-local runtime composition and lightweight glue.",
11
11
  "dependencies": {
12
- "@jskit-ai/kernel": "0.1.221"
12
+ "@jskit-ai/kernel": "0.1.222"
13
13
  },
14
14
  "jskit": {
15
15
  "kind": "runtime",