@danypops/papyrus 0.44.9 → 0.44.11
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/package.json +3 -2
- package/src/artifact/artifact.ts +23 -0
- package/src/cli/artifact-command.ts +210 -0
- package/src/cli/discuss-command.ts +256 -0
- package/src/cli/docs-command.ts +192 -0
- package/src/cli/gates-command.ts +46 -0
- package/src/cli/graph-command.ts +171 -0
- package/src/cli/graph-projection-command.ts +79 -0
- package/src/cli/log-command.ts +101 -0
- package/src/cli/migration-command.ts +40 -0
- package/src/cli/note-command.ts +186 -0
- package/src/cli/playbooks-command.ts +313 -0
- package/src/cli/rules-command.ts +220 -0
- package/src/cli/session-identity-command.ts +58 -0
- package/src/cli/shared.ts +5 -0
- package/src/cli/stricli-run.ts +30 -0
- package/src/cli/task-command.ts +892 -0
- package/src/cli.ts +30 -1912
- package/src/handlers/discuss.ts +22 -20
- package/src/handlers/docs.ts +4 -30
- package/src/handlers/notes.ts +2 -29
- package/src/handlers/playbooks.ts +27 -92
- package/src/handlers/rules.ts +4 -30
- package/src/handlers/shared.ts +92 -1
- package/src/handlers/tasks.ts +31 -118
- package/src/modules/discuss.ts +5 -26
- package/src/modules/docs.ts +6 -23
- package/src/modules/graph-projection.ts +1 -8
- package/src/modules/logs.ts +1 -22
- package/src/modules/notes.ts +1 -22
- package/src/modules/operation-input.ts +42 -0
- package/src/modules/playbooks.ts +6 -23
- package/src/modules/rules.ts +6 -23
- package/src/modules/session-identity.ts +1 -15
- package/src/modules/tasks.ts +5 -33
- package/src/service.ts +1 -28
package/src/handlers/tasks.ts
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
*
|
|
20
20
|
* remove/remove_subtree/restore are not duplicated here -- see ./artifact-trash-vehicle.ts.
|
|
21
21
|
*/
|
|
22
|
-
import { bindVehicleOperation, defineVehicleOperation, type VehicleOperationContext } from "@danypops/vehicle-core";
|
|
23
22
|
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
24
23
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
25
24
|
import type { TaskViewMode } from "../domain/task-scope.ts";
|
|
@@ -31,10 +30,10 @@ import {
|
|
|
31
30
|
classifySessionAuthorization,
|
|
32
31
|
classifyTaskDependencyCycles,
|
|
33
32
|
classifyTaskExecutionBounds,
|
|
33
|
+
createOperationDefiner,
|
|
34
|
+
definePairedMutation,
|
|
34
35
|
labelsById,
|
|
35
|
-
looseObjectSchema,
|
|
36
36
|
numberProp,
|
|
37
|
-
passthroughOutput,
|
|
38
37
|
resolveArtifactIdWidened,
|
|
39
38
|
stringProp,
|
|
40
39
|
validationError,
|
|
@@ -167,36 +166,7 @@ export function registerTasksVehicleOperations(registry: VehicleRegistry, deps:
|
|
|
167
166
|
classifySessionAuthorization(() =>
|
|
168
167
|
classifyTaskExecutionBounds(() => classifyTaskDependencyCycles(() => moduleOperations.get(name)!.execute(input))),
|
|
169
168
|
);
|
|
170
|
-
|
|
171
|
-
const define = (
|
|
172
|
-
action: string,
|
|
173
|
-
description: string,
|
|
174
|
-
effect: "read" | "local-write",
|
|
175
|
-
properties: Record<string, { type: string; enum?: readonly string[] }>,
|
|
176
|
-
required: readonly string[],
|
|
177
|
-
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
178
|
-
execute?: (input: Record<string, unknown>, context: VehicleOperationContext<Record<string, unknown>>) => unknown,
|
|
179
|
-
): void => {
|
|
180
|
-
const operation = defineVehicleOperation({
|
|
181
|
-
name: `tasks.${action}`,
|
|
182
|
-
version: 1,
|
|
183
|
-
description,
|
|
184
|
-
input: looseObjectSchema(properties, required),
|
|
185
|
-
output: passthroughOutput,
|
|
186
|
-
permissions: ["tasks:read", "tasks:write"],
|
|
187
|
-
effect,
|
|
188
|
-
idempotency: { mode: effect === "read" ? "safe" : "unsafe" },
|
|
189
|
-
limits: LIMITS,
|
|
190
|
-
});
|
|
191
|
-
registry.register(
|
|
192
|
-
OWNER,
|
|
193
|
-
bindVehicleOperation(
|
|
194
|
-
operation,
|
|
195
|
-
() => async (context) =>
|
|
196
|
-
(execute ?? ((input: Record<string, unknown>) => call(`tasks.${action}`, input)))(resolve(context.input), context),
|
|
197
|
-
),
|
|
198
|
-
);
|
|
199
|
-
};
|
|
169
|
+
const define = createOperationDefiner(registry, OWNER, "tasks", ["tasks:read", "tasks:write"], call);
|
|
200
170
|
|
|
201
171
|
/** Shared by every action taking a single id/name: resolves root_task_name first, then name -> id against the final scope. */
|
|
202
172
|
const resolveIdAndScope = (input: Record<string, unknown>): Record<string, unknown> => {
|
|
@@ -395,6 +365,7 @@ export function registerTasksVehicleOperations(registry: VehicleRegistry, deps:
|
|
|
395
365
|
(input) => input,
|
|
396
366
|
);
|
|
397
367
|
|
|
368
|
+
/** Injects the caller's own session claims (never a model-visible input field) onto every focus/pause/unpause/clear_focus call -- see this file's own doc comment. */
|
|
398
369
|
const focusOperation = (
|
|
399
370
|
action: "focus" | "pause" | "unpause" | "clear_focus",
|
|
400
371
|
description: string,
|
|
@@ -402,24 +373,10 @@ export function registerTasksVehicleOperations(registry: VehicleRegistry, deps:
|
|
|
402
373
|
required: readonly string[],
|
|
403
374
|
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
404
375
|
): void => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
description,
|
|
409
|
-
input: looseObjectSchema(properties, required),
|
|
410
|
-
output: passthroughOutput,
|
|
411
|
-
permissions: ["tasks:read", "tasks:write"],
|
|
412
|
-
effect: "local-write",
|
|
413
|
-
idempotency: { mode: "unsafe" },
|
|
414
|
-
limits: LIMITS,
|
|
376
|
+
define(action, description, "local-write", properties, required, resolve, (resolvedInput, context) => {
|
|
377
|
+
const claims = context.principal?.claims as { sessionId?: string; sessionSecret?: string } | undefined;
|
|
378
|
+
return call(`tasks.${action}`, { ...resolvedInput, session_id: claims?.sessionId, session_secret: claims?.sessionSecret });
|
|
415
379
|
});
|
|
416
|
-
registry.register(
|
|
417
|
-
OWNER,
|
|
418
|
-
bindVehicleOperation(operation, () => async (context) => {
|
|
419
|
-
const claims = context.principal?.claims as { sessionId?: string; sessionSecret?: string } | undefined;
|
|
420
|
-
return call(`tasks.${action}`, { ...resolve(context.input), session_id: claims?.sessionId, session_secret: claims?.sessionSecret });
|
|
421
|
-
}),
|
|
422
|
-
);
|
|
423
380
|
};
|
|
424
381
|
|
|
425
382
|
focusOperation(
|
|
@@ -585,34 +542,16 @@ export function registerTasksVehicleOperations(registry: VehicleRegistry, deps:
|
|
|
585
542
|
|
|
586
543
|
const scopeProp = { type: "string", enum: ["project", "graph", "all"] } as const;
|
|
587
544
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
id: stringProp,
|
|
594
|
-
name: stringProp,
|
|
595
|
-
dependency_id: stringProp,
|
|
596
|
-
dependency_name: stringProp,
|
|
597
|
-
project_root: stringProp,
|
|
598
|
-
scope: scopeProp,
|
|
599
|
-
session_id: stringProp,
|
|
600
|
-
},
|
|
601
|
-
[],
|
|
602
|
-
(input) => {
|
|
603
|
-
const filter = { projectRoot: input.project_root as string | undefined, scope: input.scope as TaskViewMode | undefined };
|
|
604
|
-
return {
|
|
605
|
-
...input,
|
|
606
|
-
id: resolveTaskId(artifacts, tasks, filter, input.id, input.name),
|
|
607
|
-
dependency_id: resolveTaskId(artifacts, tasks, filter, input.dependency_id, input.dependency_name),
|
|
608
|
-
};
|
|
609
|
-
},
|
|
610
|
-
);
|
|
545
|
+
/** Resolves a task id/name pair against the filter derived from this same input's own project_root/scope -- shared by depend/undepend and contain/uncontain below. */
|
|
546
|
+
const resolveScopedTaskId = (input: Record<string, unknown>, idProp: string, nameProp: string): string => {
|
|
547
|
+
const filter = { projectRoot: input.project_root as string | undefined, scope: input.scope as TaskViewMode | undefined };
|
|
548
|
+
return resolveTaskId(artifacts, tasks, filter, input[idProp], input[nameProp]);
|
|
549
|
+
};
|
|
611
550
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
"
|
|
551
|
+
definePairedMutation(
|
|
552
|
+
define,
|
|
553
|
+
{ idProp: "id", nameProp: "name" },
|
|
554
|
+
{ idProp: "dependency_id", nameProp: "dependency_name" },
|
|
616
555
|
{
|
|
617
556
|
id: stringProp,
|
|
618
557
|
name: stringProp,
|
|
@@ -623,44 +562,19 @@ export function registerTasksVehicleOperations(registry: VehicleRegistry, deps:
|
|
|
623
562
|
session_id: stringProp,
|
|
624
563
|
},
|
|
625
564
|
[],
|
|
626
|
-
|
|
627
|
-
const filter = { projectRoot: input.project_root as string | undefined, scope: input.scope as TaskViewMode | undefined };
|
|
628
|
-
return {
|
|
629
|
-
...input,
|
|
630
|
-
id: resolveTaskId(artifacts, tasks, filter, input.id, input.name),
|
|
631
|
-
dependency_id: resolveTaskId(artifacts, tasks, filter, input.dependency_id, input.dependency_name),
|
|
632
|
-
};
|
|
633
|
-
},
|
|
634
|
-
);
|
|
635
|
-
|
|
636
|
-
define(
|
|
637
|
-
"contain",
|
|
638
|
-
"Nests a child Task inside a parent (parent_id/parent_name contains child_id/child_name) -- explicit hierarchy, distinct from depends_on execution ordering. A name resolved outside project_root's own scope is retried once against every project before failing, unless scope is pinned explicitly.",
|
|
639
|
-
"local-write",
|
|
565
|
+
resolveScopedTaskId,
|
|
640
566
|
{
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
child_name: stringProp,
|
|
645
|
-
project_root: stringProp,
|
|
646
|
-
scope: scopeProp,
|
|
647
|
-
session_id: stringProp,
|
|
648
|
-
},
|
|
649
|
-
[],
|
|
650
|
-
(input) => {
|
|
651
|
-
const filter = { projectRoot: input.project_root as string | undefined, scope: input.scope as TaskViewMode | undefined };
|
|
652
|
-
return {
|
|
653
|
-
...input,
|
|
654
|
-
parent_id: resolveTaskId(artifacts, tasks, filter, input.parent_id, input.parent_name),
|
|
655
|
-
child_id: resolveTaskId(artifacts, tasks, filter, input.child_id, input.child_name),
|
|
656
|
-
};
|
|
567
|
+
action: "depend",
|
|
568
|
+
description:
|
|
569
|
+
"Adds a dependency edge (this task waits for dependency_id/dependency_name). Dependency edges form an executable DAG -- self-dependencies and cycles are rejected. A name resolved outside project_root's own scope is retried once against every project before failing, unless scope is pinned explicitly.",
|
|
657
570
|
},
|
|
571
|
+
{ action: "undepend", description: "Removes a dependency edge. Idempotent -- a no-op if the edge is already absent." },
|
|
658
572
|
);
|
|
659
573
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
"
|
|
574
|
+
definePairedMutation(
|
|
575
|
+
define,
|
|
576
|
+
{ idProp: "parent_id", nameProp: "parent_name" },
|
|
577
|
+
{ idProp: "child_id", nameProp: "child_name" },
|
|
664
578
|
{
|
|
665
579
|
parent_id: stringProp,
|
|
666
580
|
parent_name: stringProp,
|
|
@@ -671,14 +585,13 @@ export function registerTasksVehicleOperations(registry: VehicleRegistry, deps:
|
|
|
671
585
|
session_id: stringProp,
|
|
672
586
|
},
|
|
673
587
|
[],
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
child_id: resolveTaskId(artifacts, tasks, filter, input.child_id, input.child_name),
|
|
680
|
-
};
|
|
588
|
+
resolveScopedTaskId,
|
|
589
|
+
{
|
|
590
|
+
action: "contain",
|
|
591
|
+
description:
|
|
592
|
+
"Nests a child Task inside a parent (parent_id/parent_name contains child_id/child_name) -- explicit hierarchy, distinct from depends_on execution ordering. A name resolved outside project_root's own scope is retried once against every project before failing, unless scope is pinned explicitly.",
|
|
681
593
|
},
|
|
594
|
+
{ action: "uncontain", description: "Removes a parent/child nesting. Idempotent -- a no-op if the edge is already absent." },
|
|
682
595
|
);
|
|
683
596
|
|
|
684
597
|
define(
|
package/src/modules/discuss.ts
CHANGED
|
@@ -6,41 +6,20 @@
|
|
|
6
6
|
import type { ArtifactEventContext } from "../artifact/artifact-event.ts";
|
|
7
7
|
import type { Discussions } from "../discussion/discussion-service.ts";
|
|
8
8
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
9
|
+
import { type OperationInput, optionalNumber, optionalString, optionalStringArray } from "./operation-input.ts";
|
|
9
10
|
|
|
10
11
|
const MODULE_ID = "discuss";
|
|
11
12
|
|
|
12
|
-
type OperationInput = Record<string, unknown>;
|
|
13
|
-
|
|
14
|
-
function string(input: OperationInput, key: string): string {
|
|
15
|
-
const value = input[key];
|
|
16
|
-
if (typeof value !== "string" || value.length === 0)
|
|
17
|
-
throw new Error(`${key} is required${REQUIRED_FIELD_HINTS[key] ? ` (${REQUIRED_FIELD_HINTS[key]})` : ""}`);
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
13
|
/** Fields whose name alone doesn't say what a valid value looks like -- everything else (title, content, id, settlement) is self-explanatory. */
|
|
22
14
|
const REQUIRED_FIELD_HINTS: Record<string, string> = {
|
|
23
15
|
actor: 'a display name for who is posting, e.g. "alice" or "agent"',
|
|
24
16
|
};
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (value === undefined) return undefined;
|
|
29
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function optionalStringArray(input: OperationInput, key: string): string[] | undefined {
|
|
34
|
-
const value = input[key];
|
|
35
|
-
if (value === undefined) return undefined;
|
|
36
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${key} must be an array of strings`);
|
|
37
|
-
return value as string[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
18
|
+
/** Same contract as the shared string() in ./operation-input.ts, plus a field-specific hint on failure -- discuss's own operations are the one surface where a bare "actor is required" isn't self-explanatory. */
|
|
19
|
+
function string(input: OperationInput, key: string): string {
|
|
41
20
|
const value = input[key];
|
|
42
|
-
if (value ===
|
|
43
|
-
|
|
21
|
+
if (typeof value !== "string" || value.length === 0)
|
|
22
|
+
throw new Error(`${key} is required${REQUIRED_FIELD_HINTS[key] ? ` (${REQUIRED_FIELD_HINTS[key]})` : ""}`);
|
|
44
23
|
return value;
|
|
45
24
|
}
|
|
46
25
|
|
package/src/modules/docs.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* ArtifactStore-based with no other module's concrete class dependency.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { summarizeArtifact } from "../artifact/artifact.ts";
|
|
10
11
|
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
|
|
11
12
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
12
13
|
import type { AuthorityRegistry } from "../authority-registry.ts";
|
|
@@ -21,31 +22,10 @@ import {
|
|
|
21
22
|
updateDocument,
|
|
22
23
|
} from "../domain-services.ts";
|
|
23
24
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
25
|
+
import { type OperationInput, optionalBoolean, optionalNumber, optionalString, string } from "./operation-input.ts";
|
|
24
26
|
|
|
25
27
|
const MODULE_ID = "docs";
|
|
26
28
|
|
|
27
|
-
type OperationInput = Record<string, unknown>;
|
|
28
|
-
|
|
29
|
-
function string(input: OperationInput, key: string): string {
|
|
30
|
-
const value = input[key];
|
|
31
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
36
|
-
const value = input[key];
|
|
37
|
-
if (value === undefined) return undefined;
|
|
38
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
43
|
-
const value = input[key];
|
|
44
|
-
if (value === undefined) return undefined;
|
|
45
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
29
|
const eventContext = (input: OperationInput) => ({
|
|
50
30
|
actor: optionalString(input, "actor"),
|
|
51
31
|
source: optionalString(input, "source"),
|
|
@@ -97,7 +77,10 @@ export function docsOperations(artifacts: ArtifactStore, scopes: ArtifactScopeSt
|
|
|
97
77
|
eventContext(input),
|
|
98
78
|
),
|
|
99
79
|
),
|
|
100
|
-
define("docs.list", (input: OperationInput) =>
|
|
80
|
+
define("docs.list", (input: OperationInput) => {
|
|
81
|
+
const docs = listDocuments(artifacts, scopes, artifactFilter(input));
|
|
82
|
+
return optionalBoolean(input, "full") === true ? docs : docs.map(summarizeArtifact);
|
|
83
|
+
}),
|
|
101
84
|
define("docs.show", (input: OperationInput) => showDocument(artifacts, string(input, "id"))),
|
|
102
85
|
define("docs.activate", (input: OperationInput) =>
|
|
103
86
|
transitionDocument(artifacts, string(input, "id"), "activate", authority, eventContext(input)),
|
|
@@ -17,17 +17,10 @@ import type {
|
|
|
17
17
|
import { GraphProjection } from "../graph-projection/graph-projection-service.ts";
|
|
18
18
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
19
19
|
import type { GraphProjectionStore } from "../stores/graph-projection-store.ts";
|
|
20
|
+
import { type OperationInput, string } from "./operation-input.ts";
|
|
20
21
|
|
|
21
22
|
const MODULE_ID = "graph_projection";
|
|
22
23
|
|
|
23
|
-
type OperationInput = Record<string, unknown>;
|
|
24
|
-
|
|
25
|
-
function string(input: OperationInput, key: string): string {
|
|
26
|
-
const value = input[key];
|
|
27
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
24
|
function number(input: OperationInput, key: string): number {
|
|
32
25
|
const value = input[key];
|
|
33
26
|
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} is required and must be a number`);
|
package/src/modules/logs.ts
CHANGED
|
@@ -8,31 +8,10 @@
|
|
|
8
8
|
import type { JsonValue, LogLevel } from "../domain/log-entry.ts";
|
|
9
9
|
import type { Logs } from "../log/log-service.ts";
|
|
10
10
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
11
|
+
import { type OperationInput, optionalNumber, optionalString, string } from "./operation-input.ts";
|
|
11
12
|
|
|
12
13
|
const MODULE_ID = "logs";
|
|
13
14
|
|
|
14
|
-
type OperationInput = Record<string, unknown>;
|
|
15
|
-
|
|
16
|
-
function string(input: OperationInput, key: string): string {
|
|
17
|
-
const value = input[key];
|
|
18
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
23
|
-
const value = input[key];
|
|
24
|
-
if (value === undefined) return undefined;
|
|
25
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
30
|
-
const value = input[key];
|
|
31
|
-
if (value === undefined) return undefined;
|
|
32
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
15
|
function isJsonValue(value: unknown): value is JsonValue {
|
|
37
16
|
return (
|
|
38
17
|
value === null ||
|
package/src/modules/notes.ts
CHANGED
|
@@ -16,31 +16,10 @@
|
|
|
16
16
|
import type { NoteEventDirection } from "../domain/note-event.ts";
|
|
17
17
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
18
18
|
import type { NoteDisposition, Notes } from "../note/note-service.ts";
|
|
19
|
+
import { type OperationInput, optionalNumber, optionalString, string } from "./operation-input.ts";
|
|
19
20
|
|
|
20
21
|
const MODULE_ID = "notes";
|
|
21
22
|
|
|
22
|
-
type OperationInput = Record<string, unknown>;
|
|
23
|
-
|
|
24
|
-
function string(input: OperationInput, key: string): string {
|
|
25
|
-
const value = input[key];
|
|
26
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
31
|
-
const value = input[key];
|
|
32
|
-
if (value === undefined) return undefined;
|
|
33
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
38
|
-
const value = input[key];
|
|
39
|
-
if (value === undefined) return undefined;
|
|
40
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
41
|
-
return value;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
23
|
/** This module's own operation names, the single source of truth src/service.ts's EXPECTED_OPERATION_NAMES spreads in rather than re-listing by hand. */
|
|
45
24
|
export const NOTES_OPERATION_NAMES = [
|
|
46
25
|
"notes.capture",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared runtime validators for a Vehicle operation's raw `input` object.
|
|
3
|
+
* A Vehicle operation's own JSON-schema `input` field is descriptive metadata
|
|
4
|
+
* only (see handlers/shared.ts's looseObjectSchema) -- never itself enforced
|
|
5
|
+
* at runtime -- so every module's own operation body narrows `unknown` to
|
|
6
|
+
* the real shape it needs through these.
|
|
7
|
+
*/
|
|
8
|
+
export type OperationInput = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export function string(input: OperationInput, key: string): string {
|
|
11
|
+
const value = input[key];
|
|
12
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function optionalString(input: OperationInput, key: string): string | undefined {
|
|
17
|
+
const value = input[key];
|
|
18
|
+
if (value === undefined) return undefined;
|
|
19
|
+
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function optionalStringArray(input: OperationInput, key: string): string[] | undefined {
|
|
24
|
+
const value = input[key];
|
|
25
|
+
if (value === undefined) return undefined;
|
|
26
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${key} must be an array of strings`);
|
|
27
|
+
return value as string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
31
|
+
const value = input[key];
|
|
32
|
+
if (value === undefined) return undefined;
|
|
33
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function optionalBoolean(input: OperationInput, key: string): boolean | undefined {
|
|
38
|
+
const value = input[key];
|
|
39
|
+
if (value === undefined) return undefined;
|
|
40
|
+
if (typeof value !== "boolean") throw new Error(`${key} must be a boolean`);
|
|
41
|
+
return value;
|
|
42
|
+
}
|
package/src/modules/playbooks.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* playbook/playbook-definition.ts for the full rationale.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { summarizeArtifact } from "../artifact/artifact.ts";
|
|
13
14
|
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
|
|
14
15
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
15
16
|
import {
|
|
@@ -31,31 +32,10 @@ import type { SessionIdentity } from "../session-identity/session-identity-servi
|
|
|
31
32
|
import type { TaskEventStore } from "../stores/task-event-store.ts";
|
|
32
33
|
import type { TaskScopeStore } from "../stores/task-scope-store.ts";
|
|
33
34
|
import type { Tasks } from "../task/task-service.ts";
|
|
35
|
+
import { type OperationInput, optionalBoolean, optionalNumber, optionalString, string } from "./operation-input.ts";
|
|
34
36
|
|
|
35
37
|
const MODULE_ID = "playbooks";
|
|
36
38
|
|
|
37
|
-
type OperationInput = Record<string, unknown>;
|
|
38
|
-
|
|
39
|
-
function string(input: OperationInput, key: string): string {
|
|
40
|
-
const value = input[key];
|
|
41
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
46
|
-
const value = input[key];
|
|
47
|
-
if (value === undefined) return undefined;
|
|
48
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
49
|
-
return value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
53
|
-
const value = input[key];
|
|
54
|
-
if (value === undefined) return undefined;
|
|
55
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
56
|
-
return value;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
39
|
const eventContext = (input: OperationInput) => ({
|
|
60
40
|
actor: optionalString(input, "actor"),
|
|
61
41
|
source: optionalString(input, "source"),
|
|
@@ -135,7 +115,10 @@ export function playbooksOperations({
|
|
|
135
115
|
eventContext(input),
|
|
136
116
|
),
|
|
137
117
|
),
|
|
138
|
-
define("playbooks.list", (input: OperationInput) =>
|
|
118
|
+
define("playbooks.list", (input: OperationInput) => {
|
|
119
|
+
const playbooks = listPlaybooks(artifacts, artifactScopes, artifactFilter(input));
|
|
120
|
+
return optionalBoolean(input, "full") === true ? playbooks : playbooks.map(summarizeArtifact);
|
|
121
|
+
}),
|
|
139
122
|
define("playbooks.show", (input: OperationInput) => showPlaybook(artifacts, string(input, "id"))),
|
|
140
123
|
define("playbooks.preview", (input: OperationInput) =>
|
|
141
124
|
playbookInvocation(artifacts, string(input, "id"), input.arguments as Record<string, unknown> | undefined),
|
package/src/modules/rules.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* registry" convention.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { summarizeArtifact } from "../artifact/artifact.ts";
|
|
14
15
|
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
|
|
15
16
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
16
17
|
import {
|
|
@@ -24,31 +25,10 @@ import {
|
|
|
24
25
|
updateRule,
|
|
25
26
|
} from "../domain-services.ts";
|
|
26
27
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
28
|
+
import { type OperationInput, optionalBoolean, optionalNumber, optionalString, string } from "./operation-input.ts";
|
|
27
29
|
|
|
28
30
|
const MODULE_ID = "rules";
|
|
29
31
|
|
|
30
|
-
type OperationInput = Record<string, unknown>;
|
|
31
|
-
|
|
32
|
-
function string(input: OperationInput, key: string): string {
|
|
33
|
-
const value = input[key];
|
|
34
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
39
|
-
const value = input[key];
|
|
40
|
-
if (value === undefined) return undefined;
|
|
41
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
46
|
-
const value = input[key];
|
|
47
|
-
if (value === undefined) return undefined;
|
|
48
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
49
|
-
return value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
32
|
const eventContext = (input: OperationInput) => ({
|
|
53
33
|
actor: optionalString(input, "actor"),
|
|
54
34
|
source: optionalString(input, "source"),
|
|
@@ -100,7 +80,10 @@ export function rulesOperations(artifacts: ArtifactStore, scopes: ArtifactScopeS
|
|
|
100
80
|
eventContext(input),
|
|
101
81
|
),
|
|
102
82
|
),
|
|
103
|
-
define("rules.list", (input: OperationInput) =>
|
|
83
|
+
define("rules.list", (input: OperationInput) => {
|
|
84
|
+
const rules = listRules(artifacts, scopes, artifactFilter(input));
|
|
85
|
+
return optionalBoolean(input, "full") === true ? rules : rules.map(summarizeArtifact);
|
|
86
|
+
}),
|
|
104
87
|
define("rules.show", (input: OperationInput) => showRule(artifacts, string(input, "id"))),
|
|
105
88
|
define("rules.preview", (input: OperationInput) => previewRule(artifacts, string(input, "id"))),
|
|
106
89
|
define("rules.enable", (input: OperationInput) => transitionRule(artifacts, string(input, "id"), "enable", eventContext(input))),
|
|
@@ -5,24 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
7
7
|
import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
|
|
8
|
+
import { type OperationInput, optionalString, string } from "./operation-input.ts";
|
|
8
9
|
|
|
9
10
|
const MODULE_ID = "session-identity";
|
|
10
11
|
|
|
11
|
-
type OperationInput = Record<string, unknown>;
|
|
12
|
-
|
|
13
|
-
function string(input: OperationInput, key: string): string {
|
|
14
|
-
const value = input[key];
|
|
15
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
20
|
-
const value = input[key];
|
|
21
|
-
if (value === undefined) return undefined;
|
|
22
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
23
|
-
return value;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
12
|
/** This module's own operation names, the single source of truth src/service.ts's EXPECTED_OPERATION_NAMES spreads in rather than re-listing by hand. */
|
|
27
13
|
export const SESSION_IDENTITY_OPERATION_NAMES = ["session.register", "session.release"] as const;
|
|
28
14
|
|
package/src/modules/tasks.ts
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Task-domain-internal files (task-context.ts, task-execution.ts, domain/task-event.ts,
|
|
15
15
|
* domain/task-scope.ts) are imported directly — they belong to this bounded context,
|
|
16
|
-
* unlike a different module's infrastructure. Generic input-parsing helpers
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* unlike a different module's infrastructure. Generic input-parsing helpers come from
|
|
17
|
+
* ./operation-input.ts, a peer utility at this same modules/ layer with no domain logic
|
|
18
|
+
* of its own -- not "another module's infrastructure" or the composition root's helpers,
|
|
19
|
+
* the actual thing this module avoids importing.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
@@ -28,38 +28,10 @@ import type { SessionIdentity } from "../session-identity/session-identity-servi
|
|
|
28
28
|
import { taskContext } from "../task/task-context.ts";
|
|
29
29
|
import { projectTaskExecution } from "../task/task-execution.ts";
|
|
30
30
|
import type { TaskStatus, Tasks } from "../task/task-service.ts";
|
|
31
|
+
import { type OperationInput, optionalNumber, optionalString, optionalStringArray, string } from "./operation-input.ts";
|
|
31
32
|
|
|
32
33
|
const MODULE_ID = "tasks";
|
|
33
34
|
|
|
34
|
-
type OperationInput = Record<string, unknown>;
|
|
35
|
-
|
|
36
|
-
function string(input: OperationInput, key: string): string {
|
|
37
|
-
const value = input[key];
|
|
38
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
43
|
-
const value = input[key];
|
|
44
|
-
if (value === undefined) return undefined;
|
|
45
|
-
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function optionalStringArray(input: OperationInput, key: string): string[] | undefined {
|
|
50
|
-
const value = input[key];
|
|
51
|
-
if (value === undefined) return undefined;
|
|
52
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${key} must be an array of strings`);
|
|
53
|
-
return value as string[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
57
|
-
const value = input[key];
|
|
58
|
-
if (value === undefined) return undefined;
|
|
59
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
60
|
-
return value;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
35
|
const eventContext = (input: OperationInput): TaskEventContext => ({
|
|
64
36
|
actor: optionalString(input, "actor"),
|
|
65
37
|
source: optionalString(input, "source"),
|