@owlmeans/server-planning 0.1.18-rc.17 → 0.1.18-rc.18

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/README.md CHANGED
@@ -10,7 +10,7 @@ the `@owlmeans/server-planning/store` subpath.
10
10
  ## Installation
11
11
 
12
12
  ```sh
13
- bun add @owlmeans/server-planning@^0.1.18-rc.17 @owlmeans/planning@^0.1.18-rc.14 ajv
13
+ bun add @owlmeans/server-planning@^0.1.18-rc.18 @owlmeans/planning@^0.1.18-rc.15 ajv
14
14
  ```
15
15
 
16
16
  ## Concepts
@@ -169,7 +169,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
169
169
  your project's skill store (`.agents/skills/`):
170
170
 
171
171
  ```sh
172
- npx @owlmeans/agent-skills@^0.1.18-rc.45
172
+ npx @owlmeans/agent-skills@^0.1.18-rc.46
173
173
  ```
174
174
 
175
175
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/server-planning",
4
- "version": "0.1.18-rc.17",
5
- "generatedAt": "2026-10-02T19:07:09.538Z",
4
+ "version": "0.1.18-rc.18",
5
+ "generatedAt": "2026-10-03T11:19:50.770Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/server-planning
9
9
 
10
10
  **Layer:** Server
11
- **Install:** `"@owlmeans/server-planning": "^0.1.18-rc.17"` in `dependencies` (`ajv` is a peer)
11
+ **Install:** `"@owlmeans/server-planning": "^0.1.18-rc.18"` in `dependencies` (`ajv` is a peer)
12
12
 
13
13
  The general implementation of `@owlmeans/planning`: the planning service (a plugin host), the
14
14
  scoped facade, the executor every write goes through, the in-memory reference store, the commit
@@ -33,6 +33,7 @@ reuses the `./store` subpath (`foldPending`, `makeCommitHub`) without pulling fa
33
33
  | `scopeOf(req, extra?)`, `accessScopeOf(req, access, extra?)`, `handlerScopeOf`, `assertGranted`, `actorOf(req)`, `concealed(run)`, `clampSeconds` | Scope and security helpers |
34
34
  | `makeDefinitions(runtime, facade)`, `makeSchemaViews(opts)`, `projectCriteriaOf(projects, through?)` | Data-defined schemas and the project narrowing |
35
35
  | `makeProjectionProcessor(ctx, opts?)`, `planningQueueHooks(ctx, opts?)` | A generic projection job body and its `onJobDead` |
36
+ | `./queue`: `declarePlanningQueue(cfg, opts?)`, `PLANNING_PROJECTION_QUEUE` (`planning-projection`), `PLANNING_PROJECT_JOB` (`planning:project`), `ProjectionRequest` | The projection queue's shared address; each process chooses separately whether to listen |
36
37
  | `./store`: `makeMemoryPlanningStore`, `foldPending`, `failPending`, `revisionsFromLog`, `commitEventOf`, `makeCommitHub`, `makeCompositeStore`, `wantsSpecifications` | What stores are built from |
37
38
  | `./conformance`: `planningConformance`, `conformanceCasesFor`, `planningConformancePlugin`, `conformanceClock`, `ConformanceFailure` | The store conformance suite, runner-agnostic |
38
39
 
@@ -115,7 +116,7 @@ await planning.definitions!.define({
115
116
  | Wrong | What happens | Right |
116
117
  |---|---|---|
117
118
  | `execute({ action, draft: { … } })` | refused: `planning:malformed:create-without-draft` | the draft goes under `card` |
118
- | `execute({ …, wait: true })` | in process the key is ignored: the receipt returns before the commit, with no `card` | `execute({ … }, { wait: true })` — only the wire body (`ExecuteRequest`) carries `wait` inside |
119
+ | `execute({ …, wait: true })` | in process the key is ignored: the receipt returns before the commit, with no `card` — hidden while an inline-folding store (`planning-postgres`) lands at once, exposed the first time another process holds the card's fold | `execute({ … }, { wait: true })` — only the wire body (`ExecuteRequest`) carries `wait` inside |
119
120
  | `ctx.planning().for(…)` in a target | `ctx.planning is not a function` | `ctx.service<PlanningHostService>(PLANNING_SERVICE).for(…)` |
120
121
  | `{ id, name: 'Repair', … }` on a flow, `{ key, name, … }` on a type | `define` refuses with `SchemaInvalid` (closed schemas); a code plugin's registry does not validate, so the key is silently dropped | a flow's key is `id`, a type's is `type`, the display name is `label` on both |
121
122
  | `action: 'create'` | a type error — `TransitionAction` is an enum | `action: TransitionAction.Create` |
@@ -141,8 +142,7 @@ Nothing is appended before step 11 — every refusal leaves the log untouched.
141
142
  default store holds data-defined schemas, the card's project is found first and everything
142
143
  after reads that project's resolved layer (`Resolved.schemas`): a data-defined type of a
143
144
  project with `scopedCardTypes` is admitted, a retired type creates nothing
144
- (`UnknownWorkcardType('retired:…')`). Without the port `Resolved.schemas` IS the code registry
145
- and the order is unchanged.
145
+ (`UnknownWorkcardType('retired:…')`). Without the port `Resolved.schemas` IS the code registry.
146
146
  5. **`before` chain** — in plugin order; re-resolved once when it changed the card, type or parent.
147
147
  6. **Validate** — shape (`planning:malformed:*`), the flow rule (`IllegalTransition`), immutables
148
148
  (`planning:immutable:*`, `createdBy` among them — `assertCreatorFixed`), merged `fields` (`FieldsInvalid`), labels (`LabelNotAllowed`), a moved
@@ -164,22 +164,15 @@ Nothing is appended before step 11 — every refusal leaves the log untouched.
164
164
  in-process caller may add `agent`/`runId` on the execution. A handler never passes a wire `actor`.
165
165
 
166
166
  A create's `createdBy` is the scope's subject too — `creatorOf(scope)`: `profileId`, else `userId`,
167
- else the same two of `scope.actor`. The executor stamps it on every path, so
168
- `service.for({ entityId, profileId }).execute({ action: 'create', card })` and the HTTP `execute`
169
- produce the same owner and an ownership check (`card.createdBy === profileId`) holds for the
170
- creator. An in-process draft that names `createdBy` keeps it (explicit wins — a manager creating on
171
- someone's behalf); a scope naming nobody (a service or system scope) leaves it unset. The wire drops
172
- a create draft's `createdBy` before the stamp. Nothing else writes it: there is no `updatedBy` — who
173
- made each later write is the transition's `actor`.
174
-
175
- **After the create `createdBy` never moves.** An execution naming it in `changes` or `unset` is
176
- refused with `planning:immutable:createdBy` on every action — a create's `changes` overlay too, since
177
- the draft is its one source — whether it came over the wire, from an in-process caller, or from a
178
- `before` plugin (validation runs after the chain), and nothing is appended. The executor holds this
179
- check itself (`assertCreatorFixed`), beside `@owlmeans/planning`'s `assertMutable`, so it stands
180
- whatever planning build a host resolves. `createdAt` is an identity key and refused the same way. A
181
- row appended before the refusal existed still folds as written (the fold replays the log; guards
182
- live at admission), and the conformance suite checks a store folds it like the pure fold does.
167
+ else the same two of `scope.actor`. The executor stamps it on every path, so an in-process facade
168
+ create and the HTTP `execute` produce the same owner. An in-process draft that names `createdBy`
169
+ keeps it (explicit wins — a manager creating on someone's behalf); a scope naming nobody (a service
170
+ or system scope) leaves it unset; the wire drops a draft's `createdBy` before the stamp.
171
+
172
+ `createdBy` never moves after the create (the rule is `planning` → The record model). The executor
173
+ enforces it itself with `assertCreatorFixed` at step 6, after the `before` chain, so a wire body, an
174
+ in-process caller and a plugin are refused alike, whatever `@owlmeans/planning` build the host
175
+ resolves. The conformance suite checks a store folds a stored row naming it as the pure fold does.
183
176
 
184
177
  ## Idempotency and `expectSeq`
185
178
 
@@ -221,8 +214,8 @@ the store; `bind` replaces the listener, so a store has exactly one.
221
214
  the criteria asks for `kind: specification` or a `category` (`wantsSpecifications`, the rule every
222
215
  store routes by).
223
216
  - `schemas: true` adds the data-defined schema port (records per layer, a revision per
224
- organization, synchronous `watch`); off by default, so a plain memory store resolves everything
225
- in code exactly as before.
217
+ organization, synchronous `watch`); off by default — without it every type and flow resolves
218
+ from code.
226
219
  - `sync` (default) folds, publishes and runs `committed` inside `project()`, serialized per card.
227
220
  `sync: false` folds nothing until `flush(card?)` — a queued store's shape, for tests that need a
228
221
  commit to stay pending.
@@ -288,15 +281,13 @@ mapping external records (`mappers`) belongs to that plugin's own store adapter.
288
281
 
289
282
  - Without `opts.access`: `entityId` is `requireEntityKey(req)`; `opts.scope(req, ctx)` adds a
290
283
  `channel` or `service` and can never replace the entity. `actor` and a create's `createdBy` come
291
- from the request, never the body (`wireExecution` drops a draft's claim; the executor's
292
- `withCreator` stamps the subject, and `assertCreatorFixed` refuses a body whose `changes` or
293
- `unset` names `createdBy` — an update can never re-own a card).
294
- - The organization of a scope is the one the request ACTS IN. Behind the integrated IAM
295
- (`@owlmeans/server-iam`) that is the session's acting organization, attached to the request by the
296
- OIDC guard and keyed by its frozen `entityKey` (stable across renames), so `requireEntityKey(req)`
297
- is the right tenant key for a product whose people belong
298
- to several organizations — and, for a product with one shared organization (every person acts in
299
- the application's own), the same key for everybody. Take it from the request in the handler and
284
+ from the request, never the body (`wireExecution` drops a draft's claim, `withCreator` stamps the
285
+ subject, `assertCreatorFixed` refuses a body naming `createdBy` — an update never re-owns a card).
286
+ - The organization of a scope is the one the request ACTS IN. `requireEntityKey(req)` answers
287
+ `req.entity.id`, the stable record id (the token's `entitySlug` only where no entity resolver is
288
+ registered). Behind an OIDC relying party (`@owlmeans/server-oidc-rp`) the acting organization is
289
+ session state: the guard attaches `req.entity` with `id` = the organization claim's frozen
290
+ `entityKey`, so the tenant key survives a rename and follows a switch. Take it in the handler and
300
291
  pass it down; never read it from the context or from a body.
301
292
  - Another entity's card, specification or transition answers `WorkcardNotFound` — the same as an
302
293
  absent id — on reads and writes alike (`concealed`).
@@ -309,20 +300,20 @@ mapping external records (`mappers`) belongs to that plugin's own store adapter.
309
300
  decision per request; a throw is the request's answer (an `AuthForbidden` answers 403).
310
301
 
311
302
  - `entityId` is the resolver's — never the token's, never `opts.scope`'s.
312
- - `projects` becomes `PlanningScope.projects`, which the server facade enforces everywhere: the
313
- projects themselves and every card whose `parents` name one are visible, and so is a
314
- specification whose parent card is (its `parents` hold only that card); anything else reads as
315
- absent (`WorkcardNotFound`, `null`, an empty list), a create under an invisible parent is
316
- `ParentNotFound` (the executor's own parent load), lists, counts and summaries are AND'd with
317
- `projectCriteriaOf(projects, through)`, links and transitions are filtered by `project` (and
318
- post-filtered, for a store that ignores the field), commit subscriptions drop events outside the
319
- set and a status or wait refuses them.
320
- - A narrowed list or count admits exactly the cards a single read admits. When its criteria ask
321
- for specifications (`wantsSpecifications`) the facade resolves `through` — the parent cards a
322
- specification is seen through — with ONE read per call: the query's `parent` loaded, or else the
323
- ids of the projects' own cards. A summary counts no specification and pays nothing extra.
324
- - A person who may see only SOME projects of their organization (a lending library's member given
325
- one branch) is expressed here: the resolver answers the project ids the person's grants list as
303
+ - `projects` becomes `PlanningScope.projects`, which the server facade enforces on every read and
304
+ write. Visible are the projects, every card whose `parents` name one, and every specification
305
+ whose parent card is visible (its `parents` hold only that card). Anything else reads as absent
306
+ (`WorkcardNotFound`, `null`, an empty list); a create under an invisible parent is
307
+ `ParentNotFound`; links and transitions are filtered by `project` (and post-filtered, for a store
308
+ that ignores the field); commit subscriptions drop events outside the set and a status or wait
309
+ refuses them.
310
+ - **A narrowed list or count sees a specification whenever its parent card is visible** — exactly
311
+ what a single read admits. It is AND'd with `projectCriteriaOf(projects, through)`; when its
312
+ criteria ask for specifications (`wantsSpecifications`) the facade resolves `through` (the visible
313
+ parent cards) with ONE read per call: the query's `parent`, else the projects' own cards. A
314
+ summary counts no specification and pays nothing extra. `tests/narrowing.spec.ts` pins it.
315
+ - A person who may see only SOME projects of their organization (a tool-shed volunteer given one
316
+ shed) is expressed here: the resolver answers the project ids the person's grants list as
326
317
  `projects`, and omits the key when a grant covers every project — never a hand-written filter in
327
318
  each handler.
328
319
  - `grants` gates the stock handlers' writes: `createProjects` (a project create — `true` for the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-planning",
3
- "version": "0.1.18-rc.17",
3
+ "version": "0.1.18-rc.18",
4
4
  "license": "MIT",
5
5
  "description": "The general planning implementation — the planning service and its plugin/middleware registry, the transition executor, the in-memory store, the protocol handlers and the commit feed.",
6
6
  "type": "module",
@@ -44,21 +44,21 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@owlmeans/auth": "^0.1.18-rc.37",
48
- "@owlmeans/auth-common": "^0.1.18-rc.43",
47
+ "@owlmeans/auth": "^0.1.18-rc.38",
48
+ "@owlmeans/auth-common": "^0.1.18-rc.44",
49
49
  "@owlmeans/basic-ids": "^0.1.18-rc.34",
50
50
  "@owlmeans/context": "^0.1.18-rc.32",
51
- "@owlmeans/entrypoint": "^0.1.18-rc.38",
51
+ "@owlmeans/entrypoint": "^0.1.18-rc.39",
52
52
  "@owlmeans/error": "^0.1.18-rc.36",
53
- "@owlmeans/planning": "^0.1.18-rc.14",
54
- "@owlmeans/queue": "^0.1.18-rc.37",
53
+ "@owlmeans/planning": "^0.1.18-rc.15",
54
+ "@owlmeans/queue": "^0.1.18-rc.38",
55
55
  "@owlmeans/resource": "^0.1.18-rc.37",
56
56
  "@owlmeans/route": "^0.1.18-rc.33",
57
- "@owlmeans/server-api": "^0.1.18-rc.47",
58
- "@owlmeans/server-context": "^0.1.18-rc.39",
59
- "@owlmeans/server-entrypoint": "^0.1.18-rc.37",
60
- "@owlmeans/server-socket": "^0.1.18-rc.50",
61
- "@owlmeans/socket": "^0.1.18-rc.36"
57
+ "@owlmeans/server-api": "^0.1.18-rc.48",
58
+ "@owlmeans/server-context": "^0.1.18-rc.40",
59
+ "@owlmeans/server-entrypoint": "^0.1.18-rc.38",
60
+ "@owlmeans/server-socket": "^0.1.18-rc.51",
61
+ "@owlmeans/socket": "^0.1.18-rc.37"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "ajv": "*",