@modelprofile.com/flexharness 11.3.1 → 12.1.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.
Files changed (73) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.flexharness.core.d.ts +4 -0
  3. package/dist_ts/classes.flexharness.core.js +69 -25
  4. package/dist_ts/classes.flexharness.imports.js +4 -3
  5. package/dist_ts/classes.flexharness.reversions.d.ts +7 -1
  6. package/dist_ts/classes.flexharness.reversions.js +43 -29
  7. package/dist_ts/classes.flexharness.sessions.js +2 -6
  8. package/dist_ts/classes.stores.js +20 -18
  9. package/dist_ts/index.d.ts +1 -0
  10. package/dist_ts/index.js +2 -1
  11. package/dist_ts/interfaces.d.ts +24 -2
  12. package/dist_ts/interfaces.flexharness.d.ts +8 -1
  13. package/dist_ts/interfaces.js +1 -1
  14. package/dist_ts/plugins.d.ts +3 -3
  15. package/dist_ts/plugins.js +3 -3
  16. package/dist_ts/utils.projectionchange.d.ts +34 -0
  17. package/dist_ts/utils.projectionchange.js +135 -0
  18. package/dist_ts/utils.storecodecs.d.ts +10 -3
  19. package/dist_ts/utils.storecodecs.js +29 -4
  20. package/dist_ts/utils.storerules.d.ts +1 -1
  21. package/dist_ts/utils.storerules.js +1 -1
  22. package/dist_ts_migration/v3_legacyflexharness.js +6 -5
  23. package/dist_ts_stores_nosqldb/classes.nosqldbstores.d.ts +5 -4
  24. package/dist_ts_stores_nosqldb/classes.nosqldbstores.js +44 -25
  25. package/dist_ts_stores_nosqldb/classes.recordstore.d.ts +152 -20
  26. package/dist_ts_stores_nosqldb/classes.recordstore.js +369 -66
  27. package/dist_ts_stores_nosqldb/index.d.ts +1 -1
  28. package/dist_ts_stores_nosqldb/index.js +2 -2
  29. package/dist_ts_stores_nosqldb/plugins.d.ts +1 -1
  30. package/dist_ts_stores_nosqldb/plugins.js +1 -1
  31. package/dist_ts_stores_testing/contract.d.ts +33 -0
  32. package/dist_ts_stores_testing/contract.js +566 -0
  33. package/dist_ts_stores_testing/fixtures.d.ts +20 -0
  34. package/dist_ts_stores_testing/fixtures.js +112 -0
  35. package/dist_ts_stores_testing/index.d.ts +2 -0
  36. package/dist_ts_stores_testing/index.js +3 -0
  37. package/dist_ts_stores_testing/plugins.d.ts +5 -0
  38. package/dist_ts_stores_testing/plugins.js +7 -0
  39. package/dist_ts_typedrequest/catalog.d.ts +4 -4
  40. package/dist_ts_typedrequest/catalog.js +25 -7
  41. package/dist_ts_typedrequest/describe.js +23 -11
  42. package/dist_ts_typedrequest/interfaces.d.ts +17 -1
  43. package/package.json +9 -3
  44. package/readme.md +6 -3
  45. package/ts/00_commitinfo_data.ts +1 -1
  46. package/ts/classes.flexharness.core.ts +74 -24
  47. package/ts/classes.flexharness.imports.ts +3 -2
  48. package/ts/classes.flexharness.reversions.ts +53 -28
  49. package/ts/classes.flexharness.sessions.ts +2 -5
  50. package/ts/classes.stores.ts +40 -37
  51. package/ts/index.ts +5 -0
  52. package/ts/interfaces.flexharness.ts +8 -1
  53. package/ts/interfaces.ts +27 -2
  54. package/ts/plugins.ts +12 -7
  55. package/ts/readme.md +6 -3
  56. package/ts/utils.projectionchange.ts +190 -0
  57. package/ts/utils.storecodecs.ts +42 -7
  58. package/ts/utils.storerules.ts +2 -2
  59. package/ts_migration/v3_legacyflexharness.ts +11 -6
  60. package/ts_stores_nosqldb/classes.nosqldbstores.ts +59 -46
  61. package/ts_stores_nosqldb/classes.recordstore.ts +445 -64
  62. package/ts_stores_nosqldb/index.ts +1 -0
  63. package/ts_stores_nosqldb/plugins.ts +4 -3
  64. package/ts_stores_nosqldb/readme.md +6 -2
  65. package/ts_stores_testing/contract.ts +776 -0
  66. package/ts_stores_testing/fixtures.ts +127 -0
  67. package/ts_stores_testing/index.ts +17 -0
  68. package/ts_stores_testing/plugins.ts +20 -0
  69. package/ts_stores_testing/readme.md +62 -0
  70. package/ts_typedrequest/catalog.ts +24 -6
  71. package/ts_typedrequest/describe.ts +19 -10
  72. package/ts_typedrequest/interfaces.ts +21 -1
  73. package/ts_typedrequest/readme.md +5 -3
@@ -0,0 +1,127 @@
1
+ import * as plugins from './plugins.js';
2
+ import type {
3
+ IFlexMessage,
4
+ IFlexPermissionSnapshot,
5
+ IFlexProjectionSnapshotCurrent,
6
+ IFlexReversionSegment,
7
+ IFlexScopeSnapshot,
8
+ } from '../ts/interfaces.js';
9
+
10
+ /** A scope snapshot with one idle session. */
11
+ export function scopeSnapshot(revision: number, sessionId = 'session-1'): IFlexScopeSnapshot {
12
+ return {
13
+ schemaVersion: 1,
14
+ revision,
15
+ sessions: [
16
+ {
17
+ scopeId: 'scope-1',
18
+ sessionId,
19
+ createdAt: '2026-08-05T00:00:00.000Z',
20
+ updatedAt: '2026-08-05T00:00:00.000Z',
21
+ status: 'idle',
22
+ activity: { status: 'idle' },
23
+ },
24
+ ],
25
+ tombstones: [],
26
+ };
27
+ }
28
+
29
+ /** An empty projection. */
30
+ export function projectionSnapshot(revision: number): IFlexProjectionSnapshotCurrent {
31
+ return {
32
+ schemaVersion: 3,
33
+ revision,
34
+ messages: [],
35
+ stagedTerminals: [],
36
+ reversionSegments: [],
37
+ revertCursor: 0,
38
+ excludedRunIds: [],
39
+ pendingReversionReleases: [],
40
+ };
41
+ }
42
+
43
+ /** The user and assistant message of one completed run of `session-1`. */
44
+ export function runMessages(runId: string): IFlexMessage[] {
45
+ return [{
46
+ messageId: `user-${runId}`,
47
+ sessionId: 'session-1',
48
+ runId,
49
+ role: 'user',
50
+ status: 'completed',
51
+ createdAt: '2026-08-05T00:00:00.000Z',
52
+ completedAt: '2026-08-05T00:00:01.000Z',
53
+ parts: [{ partId: `user-part-${runId}`, type: 'text', text: runId }],
54
+ }, {
55
+ messageId: `assistant-${runId}`,
56
+ sessionId: 'session-1',
57
+ runId,
58
+ role: 'assistant',
59
+ status: 'completed',
60
+ createdAt: '2026-08-05T00:00:00.000Z',
61
+ completedAt: '2026-08-05T00:00:01.000Z',
62
+ parts: [{ partId: `assistant-part-${runId}`, type: 'text', text: 'answer' }],
63
+ }];
64
+ }
65
+
66
+ /** The reversion segment of one completed run, without a workspace capture. */
67
+ export function runSegment(runId: string): IFlexReversionSegment {
68
+ return {
69
+ runId,
70
+ userMessageId: `user-${runId}`,
71
+ status: 'completed',
72
+ contextAvailable: true,
73
+ eventIds: [`event-${runId}`],
74
+ workspaceCaptured: false,
75
+ protocolVersion: 1,
76
+ provenance: 'transcript',
77
+ };
78
+ }
79
+
80
+ /** A projection of completed runs of `session-1`, each with its messages and reversion segment. */
81
+ export function projectionWithRuns(revision: number, runIds: string[]): IFlexProjectionSnapshotCurrent {
82
+ return {
83
+ ...projectionSnapshot(revision),
84
+ messages: runIds.flatMap(runMessages),
85
+ reversionSegments: runIds.map(runSegment),
86
+ revertCursor: runIds.length,
87
+ };
88
+ }
89
+
90
+ /** A permission snapshot that remembers one approval. */
91
+ export function permissionSnapshot(revision: number): IFlexPermissionSnapshot {
92
+ return {
93
+ schemaVersion: 1,
94
+ revision,
95
+ rememberedPermissionKeys: ['shell:approved'],
96
+ };
97
+ }
98
+
99
+ /** A system message event. */
100
+ export function agentEvent(id = 'event-1', content = 'System prompt'): plugins.ISystemMessageEvent {
101
+ return plugins.createAgentEvent<plugins.ISystemMessageEvent>({
102
+ id,
103
+ timestamp: 1,
104
+ type: 'system-message',
105
+ content,
106
+ });
107
+ }
108
+
109
+ /** The Agent event change that appends `events`. */
110
+ export function appendEvents(...events: plugins.TAgentEvent[]): plugins.IAgentEventChange {
111
+ return { remove: [], replace: [], append: events };
112
+ }
113
+
114
+ /** A finished shell job. */
115
+ export function finishedJob(executionId = 'job-1'): plugins.IToolJobState {
116
+ return {
117
+ executionId,
118
+ type: 'shell',
119
+ state: 'finished',
120
+ exitCode: 0,
121
+ stdout: 'ok',
122
+ stderr: '',
123
+ startedAt: 1,
124
+ updatedAt: 2,
125
+ finishedAt: 2,
126
+ };
127
+ }
@@ -0,0 +1,17 @@
1
+ export {
2
+ createFlexStoreContractTests,
3
+ type IFlexStoreContractBackend,
4
+ type IFlexStoreContractFixture,
5
+ type IFlexStoreContractTest,
6
+ } from './contract.js';
7
+ export {
8
+ agentEvent,
9
+ appendEvents,
10
+ finishedJob,
11
+ permissionSnapshot,
12
+ projectionSnapshot,
13
+ projectionWithRuns,
14
+ runMessages,
15
+ runSegment,
16
+ scopeSnapshot,
17
+ } from './fixtures.js';
@@ -0,0 +1,20 @@
1
+ // node native scope
2
+ import { strict as assert } from 'node:assert';
3
+
4
+ export { assert };
5
+
6
+ // @modelprofile.com scope
7
+ import {
8
+ AgentEventStoreConflictError,
9
+ ToolJobStoreConflictError,
10
+ createAgentEvent,
11
+ } from '@modelprofile.com/flexharness-agent';
12
+
13
+ export { AgentEventStoreConflictError, ToolJobStoreConflictError, createAgentEvent };
14
+ export type {
15
+ IAgentEventArchive,
16
+ IAgentEventChange,
17
+ ISystemMessageEvent,
18
+ IToolJobState,
19
+ TAgentEvent,
20
+ } from '@modelprofile.com/flexharness-agent';
@@ -0,0 +1,62 @@
1
+ The store contract of FlexHarness as test cases for any test runner: the behaviour every built-in `IFlexHarnessStores` bundle passes, to run against a custom one.
2
+
3
+ ```sh
4
+ pnpm add @modelprofile.com/flexharness
5
+ ```
6
+
7
+ ```typescript
8
+ import { test } from 'node:test';
9
+ import { createFlexStoreContractTests } from '@modelprofile.com/flexharness/stores/testing';
10
+
11
+ for (const contractTest of createFlexStoreContractTests({
12
+ name: 'my stores',
13
+ sharedBackingStore: true,
14
+ releasesSessions: false,
15
+ storesResults: false,
16
+ async open() {
17
+ const database = await createDisposableDatabase();
18
+ return {
19
+ stores: new MyFlexHarnessStores(database),
20
+ openPeer: async () => new MyFlexHarnessStores(database.connectAgain()),
21
+ close: () => database.drop(),
22
+ };
23
+ },
24
+ })) {
25
+ test(contractTest.title, () => contractTest.run());
26
+ }
27
+ ```
28
+
29
+ `createFlexStoreContractTests(backend)` returns one `{ title, run() }` per case; `run()` resolves when the case passes and throws a `node:assert` error, or the store's own error, when it fails. Each case calls `backend.open()` for a fixture over an empty backing store and closes it afterwards, also when it fails, so the backend needs no cleanup between cases and the cases may run in any order. The backend declares what its bundle offers, and the cases that need more are left out:
30
+
31
+ - `sharedBackingStore`: several bundles can open one backing store, as separate processes would. The fixture's `openPeer()` opens another, and a case checks that the bundles share compare-and-swap revisions.
32
+ - `releasesSessions`: the Agent event and tool job providers implement `releaseSession`, which must evict their session wrappers without deleting stored data.
33
+ - `storesResults`: the bundle provides the optional `results` store.
34
+
35
+ The cases cover compare-and-swap revisions and the conflict errors that report them, cloning at the store boundary, session and storage key isolation, the snapshot validation every domain applies, and the change-based saves of projections and Agent events: a projection change keeps the stored entries its list changes name and appends the rest, an Agent event change removes, replaces and appends events, and a change that does not fit the stored value never loads, whether the store rejects it when it saves the change or when it next loads the value. The cases await every store call, as FlexHarness does, so a store method may reject or throw.
36
+
37
+ A store that keeps snapshots implements the change-based saves with the helpers of the root and Agent packages: `assertFlexProjectionChange()` and `applyFlexProjectionChange()` from `@modelprofile.com/flexharness`, `assertAgentEventChange()` and `applyAgentEventChange()` from `@modelprofile.com/flexharness-agent`, followed by the snapshot validators the root readme lists. The fixture builders the cases use, such as `projectionWithRuns()` and `appendEvents()`, are exported too, for a backend's own tests.
38
+
39
+ ## Issue Reporting and Security
40
+
41
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
42
+
43
+ ## License and Legal Information
44
+
45
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
46
+
47
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
48
+
49
+ ### Trademarks
50
+
51
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
52
+
53
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
54
+
55
+ ### Company Information
56
+
57
+ Task Venture Capital GmbH<br>
58
+ Registered at District Court Bremen HRB 35230 HB, Germany
59
+
60
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
61
+
62
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -27,10 +27,10 @@ export class FlexTypedRequestError extends Error {
27
27
  /**
28
28
  * The tool catalog of a typedrequest surface: every included method becomes a catalog method of
29
29
  * its path, declared as its contract writes it and validated against its schema, and runs through
30
- * `router.routeAndAddResponse()` in process, its request the model's parameters with the omitted
31
- * properties `request` fills and its result what `response` makes of the answer. A refusal rejects
32
- * with `FlexTypedRequestError`. A method `readOnly` marks is declared read-only and not gated by
33
- * `onCall`.
30
+ * `router.routeAndAddResponse()` in process, its request what `params` makes of the model's
31
+ * parameters with the omitted properties its request has as `request` fills them, and its result
32
+ * what `response` makes of the answer. A refusal rejects with `FlexTypedRequestError`. A method
33
+ * `readOnly` marks is declared read-only and not gated by `onCall`.
34
34
  */
35
35
  export function catalogFromTypedRequests<TScope = unknown>(
36
36
  surface: IFlexTypedRequestSurface,
@@ -39,6 +39,12 @@ export function catalogFromTypedRequests<TScope = unknown>(
39
39
  if (typeof options?.router?.routeAndAddResponse !== 'function') {
40
40
  throw new plugins.FlexHarnessValidationError('catalogFromTypedRequests needs a router with routeAndAddResponse().');
41
41
  }
42
+ const unlisted = surface.methods.find((method) => !Array.isArray(method.omitted));
43
+ if (unlisted) {
44
+ throw new plugins.FlexHarnessValidationError(
45
+ `The surface's method ${unlisted.method} has no omitted list, as one an earlier version described; describe it again with this version's describeTypedRequests().`,
46
+ );
47
+ }
42
48
  const aliases = options.aliases ?? {};
43
49
  const known = new Set(surface.methods.map((method) => method.method));
44
50
  const unknown = Object.keys(aliases).filter((method) => !known.has(method));
@@ -58,12 +64,24 @@ export function catalogFromTypedRequests<TScope = unknown>(
58
64
  params: Record<string, unknown>,
59
65
  context: IFlexCatalogRunContext<TScope>,
60
66
  ): Promise<unknown> => {
61
- const filled = options.request ? await options.request(method, context) : {};
67
+ const mapped = options.params ? await options.params(method, params, context) : params;
68
+ if (typeof mapped !== 'object' || mapped === null || Array.isArray(mapped)) {
69
+ throw new plugins.FlexHarnessValidationError(`The options' params() made no request object for ${method.method}.`);
70
+ }
71
+ // An omitted property is the binding's alone: the model's value never reaches the router.
72
+ const request: Record<string, unknown> = { ...mapped };
73
+ for (const key of surface.omit) delete request[key];
74
+ if (method.omitted.length > 0) {
75
+ const filled = options.request ? await options.request(method, context) : {};
76
+ for (const key of method.omitted) {
77
+ if (Object.hasOwn(filled, key)) request[key] = filled[key];
78
+ }
79
+ }
62
80
  const localData = options.localData ? await options.localData(method, context) : undefined;
63
81
  const answer = await router.routeAndAddResponse(
64
82
  {
65
83
  method: method.method,
66
- request: { ...params, ...filled },
84
+ request,
67
85
  response: {},
68
86
  correlation: { id: plugins.crypto.randomUUID(), phase: 'request' },
69
87
  },
@@ -43,7 +43,7 @@ const withoutOmitted = (
43
43
  const target = referencedType(schema);
44
44
  if (target !== undefined) {
45
45
  const named = types[target];
46
- return named !== undefined && holdsOmitted(named, omit, types, new Set([target]))
46
+ return named !== undefined && omittedIn(named, omit, types, new Set([target])).size > 0
47
47
  ? withoutOmitted(named, omit, types)
48
48
  : schema;
49
49
  }
@@ -63,22 +63,29 @@ const withoutOmitted = (
63
63
  return stripped;
64
64
  };
65
65
 
66
- /** Whether a schema has an omitted property, itself or through the schemas it composes or refers to. */
67
- const holdsOmitted = (
66
+ /** The omitted properties a schema has, itself or through the schemas it composes or refers to. */
67
+ const omittedIn = (
68
68
  schema: TFlexTypedRequestSchema,
69
69
  omit: ReadonlySet<string>,
70
70
  types: Readonly<Record<string, TFlexTypedRequestSchema>>,
71
71
  seen: Set<string>,
72
- ): boolean => {
73
- if (isRecord(schema.properties) && Object.keys(schema.properties).some((key) => omit.has(key))) return true;
72
+ found = new Set<string>(),
73
+ ): Set<string> => {
74
+ if (isRecord(schema.properties)) {
75
+ for (const key of Object.keys(schema.properties)) if (omit.has(key)) found.add(key);
76
+ }
74
77
  const target = referencedType(schema);
75
78
  if (target !== undefined) {
76
- if (seen.has(target) || types[target] === undefined) return false;
79
+ if (seen.has(target) || types[target] === undefined) return found;
77
80
  seen.add(target);
78
- return holdsOmitted(types[target], omit, types, seen);
81
+ return omittedIn(types[target], omit, types, seen, found);
79
82
  }
80
- return (['allOf', 'anyOf', 'oneOf'] as const).some((key) => Array.isArray(schema[key])
81
- && (schema[key] as unknown[]).some((member) => isRecord(member) && holdsOmitted(member, omit, types, seen)));
83
+ for (const key of ['allOf', 'anyOf', 'oneOf'] as const) {
84
+ const members = schema[key];
85
+ if (!Array.isArray(members)) continue;
86
+ for (const member of members) if (isRecord(member)) omittedIn(member, omit, types, seen, found);
87
+ }
88
+ return found;
82
89
  };
83
90
 
84
91
  /** Whether a call may leave out every parameter: its schema requires none. */
@@ -169,7 +176,8 @@ export async function describeTypedRequests(options: IFlexTypedRequestSurfaceOpt
169
176
  let path = base;
170
177
  for (let suffix = 2; paths.has(path); suffix++) path = `${base}_${suffix}`;
171
178
  paths.add(path);
172
- const inputSchema = withoutOmitted(withDefinitionRefs(contract.request) as TFlexTypedRequestSchema, omitted, schemas);
179
+ const requestSchema = withDefinitionRefs(contract.request) as TFlexTypedRequestSchema;
180
+ const inputSchema = withoutOmitted(requestSchema, omitted, schemas);
173
181
  const docLines = [
174
182
  ...(declared.documentation ? [declared.documentation] : []),
175
183
  ...(declared.request.documentation ? [`@param params ${declared.request.documentation}`] : []),
@@ -183,6 +191,7 @@ export async function describeTypedRequests(options: IFlexTypedRequestSurfaceOpt
183
191
  summary: summaryOf(declared.documentation, contract.method),
184
192
  ...(declared.documentation ? { description: declared.documentation } : {}),
185
193
  ...(contract.deprecated ? { deprecated: true } : {}),
194
+ omitted: [...omittedIn(requestSchema, omitted, schemas, new Set())].sort(),
186
195
  inputSchema,
187
196
  outputSchema: withDefinitionRefs(contract.response) as TFlexTypedRequestSchema,
188
197
  declaration: `${docLines.length ? docBlock(docLines) : ''}function ${functionName}(params${optional}: ${declared.request.type}): Promise<${declared.response.type}>;`,
@@ -42,6 +42,11 @@ export interface IFlexTypedRequestMethod {
42
42
  description?: string;
43
43
  /** Set by a `@deprecated` tag on the contract. */
44
44
  deprecated?: boolean;
45
+ /**
46
+ * The properties of the surface's `omit` this method's request has, itself or through the types
47
+ * it refers to: the ones the binding fills for its calls. Empty where it has none.
48
+ */
49
+ omitted: string[];
45
50
  /** The request's schema, without the omitted properties. */
46
51
  inputSchema: TFlexTypedRequestSchema;
47
52
  /** The response's schema. */
@@ -96,7 +101,9 @@ export interface IFlexTypedRequestCatalogOptions<TScope = unknown> {
96
101
  router: IFlexTypedRequestRouter;
97
102
  /**
98
103
  * The request properties the surface leaves out, from the run: `{ identity }` of the user the
99
- * session acts for. They are merged over the model's parameters.
104
+ * session acts for. A call takes from it the properties its method's request has
105
+ * (`IFlexTypedRequestMethod.omitted`) and nothing else; a method whose request has none is called
106
+ * without asking.
100
107
  */
101
108
  request?(
102
109
  method: IFlexTypedRequestMethod,
@@ -116,6 +123,19 @@ export interface IFlexTypedRequestCatalogOptions<TScope = unknown> {
116
123
  * `IFlexCatalogMethod.readOnly`. Default: none.
117
124
  */
118
125
  readOnly?(method: IFlexTypedRequestMethod): boolean;
126
+ /**
127
+ * The request a call routes for the model's validated parameters, such as the parameters with the
128
+ * short names the model was shown for records turned back into their IDs. Default: the
129
+ * parameters. It runs when the call runs, after `onCall` decided it where the method has one:
130
+ * `onCall` and a proposal see the model's parameters. The omitted properties are dropped from what
131
+ * it returns, as from the model's, and only `request` fills them. A throw refuses the call with
132
+ * its error, and so does a result that is no object.
133
+ */
134
+ params?(
135
+ method: IFlexTypedRequestMethod,
136
+ params: Record<string, unknown>,
137
+ context: IFlexCatalogRunContext<TScope>,
138
+ ): Record<string, unknown> | PromiseLike<Record<string, unknown>>;
119
139
  /**
120
140
  * What the model sees of a method's answered response, such as the response without a
121
141
  * credential's secret. Default: the response. A refusal does not reach it, and the method's
@@ -29,19 +29,21 @@ const catalog = catalogFromTypedRequests<IAppScope>(surface, {
29
29
  - Each typed method becomes a method of the path `<area>.<method>`; the area is the base name of the file that declares the contract (`vouchers` for `requests/vouchers.ts`, the folder's name for an `index.ts`), or what `area` returns. A name that is no identifier is mapped as `FlexToolCatalog.identifierOf()` maps it (`export` becomes `export_`).
30
30
  - Its declaration is the contract as written: `function getVoucher(params: { voucherId: string }): Promise<{ voucher: IVoucher }>;` after the contract's JSDoc with its tags, the request property's JSDoc as `@param params`, the response property's as `@returns`. `params` is optional where the request requires nothing.
31
31
  - Its `inputSchema` and `outputSchema` are the request's and response's JSON Schema; `execute` validates every call against the request's.
32
- - `omit` names request properties the binding fills from the run, such as the caller's identity or a CSRF token: they leave every request's schema and declaration, so the model can neither see nor send them.
32
+ - `omit` names request properties the binding fills from the run, such as the caller's identity or a CSRF token: they leave every request's schema and declaration, so the model can neither see nor send them. Each method lists in `omitted` those its request has, itself or through the types it refers to; a public method's list may be empty.
33
33
  - The named types the requests and responses reach become the catalog's types, each with its JSON Schema, its declaration from the sources, or both: a generic is declared once (`IPage<T>`) and its instances have schemas (`IPage_IVoucher`). A method's result types are declared only as deep as `toolCatalog.outputTypeDepth` asks, as for any declaration.
34
34
 
35
35
  The surface is plain JSON, so a build step can write it next to the server and the server read it at startup without TypeScript or `@api.global/typedopenapi`. A 330-method API is described in about 8 seconds.
36
36
 
37
37
  `catalogFromTypedRequests(surface, options)` returns an `IFlexToolCatalog`:
38
38
 
39
- - `router` answers the calls: anything with `routeAndAddResponse()`, such as a `TypedRouter` of `@api.global/typedrequest`. A call is routed as a typed request with the model's parameters and what `request` returns for the omitted properties, `localData` as the trusted route data a transport would hand the handlers, and the run's abort signal.
39
+ - `router` answers the calls: anything with `routeAndAddResponse()`, such as a `TypedRouter` of `@api.global/typedrequest`. A call is routed as a typed request with the model's parameters, `localData` as the trusted route data a transport would hand the handlers, and the run's abort signal.
40
+ - `request` fills the omitted properties. A call takes from what it returns only the omitted properties its method's request has, so one answer serves every method (`{ identity, csrfToken }`), and a method whose request has none is routed without asking it. A value the model passes for any omitted property never reaches the router.
40
41
  - A refusal rejects with `FlexTypedRequestError`: the typed method, the error's `text` and `data`, as a client of the API would receive them. A handler's own failure arrives as the router's internal error. A host's `externalErrorProjector` decides what of it the model sees.
41
42
  - `include` picks the methods the model may use, by default every method that is not deprecated; `aliases` gives methods other names by typed method; `vocabulary`, `onCall` and `onProposals` are the catalog's own (`onCall` receives the typed method first).
42
43
  - `readOnly` marks the methods that only read. A marked method is declared `readOnly` and `onCall` does not decide its calls, so they run concurrently with other reads, may start while the model still streams, and a turn answers identical ones once; every other method's calls, gated by `onCall` or not, run in the order the model made them. Mark nothing that asks for permission.
44
+ - `params` makes the request a call routes of the model's validated parameters, such as the parameters with the short names the model was shown for records turned back into their IDs; by default the parameters themselves. It runs when the call runs, after `onCall` decided it where the method has one, so `onCall` and a proposal see the model's parameters. The omitted properties are dropped from what it returns as from the model's, and only `request` fills them. A throw refuses the call with its error, and so does a result that is no object.
43
45
  - `response` decides what the model sees of an answered response, such as the response without a credential's secret; by default the response itself. A refusal does not reach it, and the method's declaration still shows the contract's response.
44
- - A surface that omits properties needs `request`; an alias for a method the surface lacks is refused.
46
+ - A surface that omits properties needs `request`; an alias for a method the surface lacks is refused, and so is a surface whose methods have no `omitted` list, as one described by an earlier version: describe it again.
45
47
 
46
48
  ## Issue Reporting and Security
47
49