@danypops/papyrus 0.42.0 → 0.42.2

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/package.json +2 -2
  2. package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
  3. package/src/adapters/sqlite-artifact-store.ts +7 -5
  4. package/src/adapters/sqlite-discussion-round-store.ts +26 -17
  5. package/src/adapters/sqlite-gate-runner.ts +1 -1
  6. package/src/adapters/sqlite-graph-projection-store.ts +14 -10
  7. package/src/adapters/sqlite-log-store.ts +36 -17
  8. package/src/adapters/sqlite-note-event-store.ts +20 -16
  9. package/src/adapters/sqlite-session-identity-store.ts +13 -7
  10. package/src/adapters/sqlite-task-event-store.ts +29 -21
  11. package/src/adapters/sqlite-task-focus-store.ts +36 -10
  12. package/src/adapters/sqlite-task-lease-store.ts +12 -6
  13. package/src/adapters/sqlite-task-scope-store.ts +25 -14
  14. package/src/artifact-relationship-view.ts +3 -3
  15. package/src/artifact-subtree.ts +4 -2
  16. package/src/authority-registry.ts +2 -1
  17. package/src/cli.ts +785 -179
  18. package/src/client.ts +46 -20
  19. package/src/constants.ts +15 -4
  20. package/src/daemon-state.ts +4 -12
  21. package/src/daemon.ts +31 -9
  22. package/src/db.ts +119 -98
  23. package/src/discussion-service.ts +109 -44
  24. package/src/domain/artifact-event.ts +17 -4
  25. package/src/domain/artifact.ts +3 -1
  26. package/src/domain/blueprint-definition.ts +26 -31
  27. package/src/domain/checklist.ts +20 -17
  28. package/src/domain/discussion.ts +37 -18
  29. package/src/domain/gate.ts +7 -7
  30. package/src/domain/log-entry.ts +1 -1
  31. package/src/domain/note-event.ts +20 -7
  32. package/src/domain/task-event.ts +17 -7
  33. package/src/domain-services.ts +241 -110
  34. package/src/graph-projection-service.ts +34 -8
  35. package/src/id-migration.ts +17 -4
  36. package/src/index.ts +16 -11
  37. package/src/log-service.ts +6 -5
  38. package/src/log.ts +19 -0
  39. package/src/modules/discuss.ts +63 -28
  40. package/src/modules/docs.ts +74 -17
  41. package/src/modules/graph-projection.ts +20 -9
  42. package/src/modules/logs.ts +33 -21
  43. package/src/modules/notes.ts +66 -28
  44. package/src/modules/playbooks.ts +88 -29
  45. package/src/modules/rules.ts +57 -15
  46. package/src/modules/session-identity.ts +6 -2
  47. package/src/modules/tasks.ts +142 -67
  48. package/src/note-service.ts +11 -7
  49. package/src/ops.ts +131 -68
  50. package/src/playbook-definition.ts +56 -17
  51. package/src/playbook-execution.ts +13 -3
  52. package/src/ports/note-event-store.ts +6 -4
  53. package/src/ports/task-event-store.ts +9 -6
  54. package/src/ports/task-focus-store.ts +17 -4
  55. package/src/ports/task-lease-store.ts +9 -4
  56. package/src/ports/task-scope-store.ts +3 -1
  57. package/src/service.ts +143 -89
  58. package/src/session-identity-service.ts +10 -2
  59. package/src/task-context.ts +28 -16
  60. package/src/task-execution.ts +4 -12
  61. package/src/task-graph-view.ts +12 -12
  62. package/src/task-relationship-view.ts +1 -3
  63. package/src/task-service.ts +167 -72
  64. package/src/vehicle/artifact-trash-vehicle.ts +25 -13
  65. package/src/vehicle/artifact-vehicle-shared.ts +28 -7
  66. package/src/vehicle/docs-vehicle.ts +48 -16
  67. package/src/vehicle/notes-vehicle.ts +24 -6
  68. package/src/vehicle/papyrus-vehicle.ts +14 -3
  69. package/src/vehicle/playbooks-vehicle.ts +87 -18
  70. package/src/vehicle/rules-vehicle.ts +58 -21
  71. package/src/vehicle/tasks-vehicle.ts +366 -54
  72. package/src/version.ts +1 -1
  73. package/src/workflow-execution.ts +71 -52
@@ -3,12 +3,17 @@
3
3
  * contexts. See src/domain/graph-projection.ts for the batch/checkpoint shapes and the
4
4
  * documented, deliberate scope limits of this first walking-skeleton slice.
5
5
  */
6
- import { GRAPH_PROJECTION_ID_MAX_LENGTH, GRAPH_PROJECTION_MAX_ARTIFACTS_PER_BATCH, GRAPH_PROJECTION_MAX_EDGES_PER_BATCH } from "./constants.ts";
6
+
7
+ import type { AuthorityRegistry } from "./authority-registry.ts";
8
+ import {
9
+ GRAPH_PROJECTION_ID_MAX_LENGTH,
10
+ GRAPH_PROJECTION_MAX_ARTIFACTS_PER_BATCH,
11
+ GRAPH_PROJECTION_MAX_EDGES_PER_BATCH,
12
+ } from "./constants.ts";
7
13
  import { GRAPH_PROJECTION_SCHEMA_VERSION, type GraphProjectionBatch, type GraphProjectionResult } from "./domain/graph-projection.ts";
8
14
  import type { ArtifactStore } from "./ports/artifact-store.ts";
9
15
  import { requireAtomicArtifactStore } from "./ports/atomic-artifact-store.ts";
10
16
  import type { GraphProjectionStore } from "./ports/graph-projection-store.ts";
11
- import type { AuthorityRegistry } from "./authority-registry.ts";
12
17
 
13
18
  function boundedId(value: string, label: string): string {
14
19
  if (!value || value.length === 0) throw new Error(`${label} is required`);
@@ -29,7 +34,9 @@ export class GraphProjection {
29
34
 
30
35
  apply(batch: GraphProjectionBatch): GraphProjectionResult {
31
36
  if (batch.schemaVersion !== GRAPH_PROJECTION_SCHEMA_VERSION) {
32
- throw new Error(`unsupported graph projection schema version "${batch.schemaVersion}", expected "${GRAPH_PROJECTION_SCHEMA_VERSION}"`);
37
+ throw new Error(
38
+ `unsupported graph projection schema version "${batch.schemaVersion}", expected "${GRAPH_PROJECTION_SCHEMA_VERSION}"`,
39
+ );
33
40
  }
34
41
  const producerId = boundedId(batch.producerId, "producer_id");
35
42
  const batchId = boundedId(batch.batchId, "batch_id");
@@ -41,20 +48,35 @@ export class GraphProjection {
41
48
  throw new Error(`batch is bounded to ${GRAPH_PROJECTION_MAX_EDGES_PER_BATCH} edges; got ${batch.edges.length}`);
42
49
  }
43
50
  for (const artifact of batch.artifacts) boundedId(artifact.externalId, "artifact externalId");
44
- for (const edge of batch.edges) { boundedId(edge.from, "edge from"); boundedId(edge.to, "edge to"); }
51
+ for (const edge of batch.edges) {
52
+ boundedId(edge.from, "edge from");
53
+ boundedId(edge.to, "edge to");
54
+ }
45
55
 
46
56
  const existingCheckpoint = this.store.getCheckpoint(producerId);
47
57
  if (existingCheckpoint?.lastBatchId === batchId && existingCheckpoint.lastSequence === batch.sequence) {
48
- return { producerId, batchId, sequence: batch.sequence, artifactsUpserted: 0, artifactsCreated: 0, edgesUpserted: 0, alreadyApplied: true };
58
+ return {
59
+ producerId,
60
+ batchId,
61
+ sequence: batch.sequence,
62
+ artifactsUpserted: 0,
63
+ artifactsCreated: 0,
64
+ edgesUpserted: 0,
65
+ alreadyApplied: true,
66
+ };
49
67
  }
50
68
  if (existingCheckpoint === null) {
51
69
  if (batch.sequence !== 1) throw new Error(`first batch for producer "${producerId}" must have sequence 1, got ${batch.sequence}`);
52
70
  } else {
53
71
  if (batch.sequence <= existingCheckpoint.lastSequence) {
54
- throw new Error(`stale batch: sequence ${batch.sequence} is not after checkpoint sequence ${existingCheckpoint.lastSequence} for producer "${producerId}"`);
72
+ throw new Error(
73
+ `stale batch: sequence ${batch.sequence} is not after checkpoint sequence ${existingCheckpoint.lastSequence} for producer "${producerId}"`,
74
+ );
55
75
  }
56
76
  if (batch.sequence > existingCheckpoint.lastSequence + 1) {
57
- throw new Error(`sequence gap for producer "${producerId}": expected ${existingCheckpoint.lastSequence + 1}, got ${batch.sequence}`);
77
+ throw new Error(
78
+ `sequence gap for producer "${producerId}": expected ${existingCheckpoint.lastSequence + 1}, got ${batch.sequence}`,
79
+ );
58
80
  }
59
81
  }
60
82
 
@@ -71,7 +93,11 @@ export class GraphProjection {
71
93
  for (const projected of batch.artifacts) {
72
94
  const existingId = this.store.resolveIdentity(producerId, projected.externalId);
73
95
  if (existingId) {
74
- this.artifacts.updateContent(existingId, { title: projected.title, body: projected.body, labels: projected.labels ? [...projected.labels] : undefined });
96
+ this.artifacts.updateContent(existingId, {
97
+ title: projected.title,
98
+ body: projected.body,
99
+ labels: projected.labels ? [...projected.labels] : undefined,
100
+ });
75
101
  if (projected.extra !== undefined) this.artifacts.setExtra(existingId, projected.extra);
76
102
  } else {
77
103
  const created = this.artifacts.create({
@@ -71,7 +71,12 @@ const TEXT_SCAN_COLUMNS: ReadonlyArray<{ table: string; column: string }> = [
71
71
  ];
72
72
 
73
73
  /** Audit tables whose append-only guard must be suspended for exactly this migration's duration. */
74
- const APPEND_ONLY_GUARD_TRIGGERS = ["task_events_no_update", "task_events_no_delete", "artifact_events_no_update", "artifact_events_no_delete"];
74
+ const APPEND_ONLY_GUARD_TRIGGERS = [
75
+ "task_events_no_update",
76
+ "task_events_no_delete",
77
+ "artifact_events_no_update",
78
+ "artifact_events_no_delete",
79
+ ];
75
80
 
76
81
  function tableExists(db: Db, table: string): boolean {
77
82
  return db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(table) != null;
@@ -102,8 +107,13 @@ export function applyIdMigration(db: Db, plan: IdMigrationPlan): IdMigrationRepo
102
107
  return inTransaction(db, () => {
103
108
  const triggerDdl = new Map<string, string>();
104
109
  for (const name of APPEND_ONLY_GUARD_TRIGGERS) {
105
- const row = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'trigger' AND name = ?").get(name) as { sql: string } | undefined;
106
- if (row) { triggerDdl.set(name, row.sql); db.exec(`DROP TRIGGER ${name}`); }
110
+ const row = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'trigger' AND name = ?").get(name) as
111
+ | { sql: string }
112
+ | undefined;
113
+ if (row) {
114
+ triggerDdl.set(name, row.sql);
115
+ db.exec(`DROP TRIGGER ${name}`);
116
+ }
107
117
  }
108
118
  try {
109
119
  let edgesRemapped = 0;
@@ -123,7 +133,10 @@ export function applyIdMigration(db: Db, plan: IdMigrationPlan): IdMigrationRepo
123
133
  let textOccurrencesRemapped = 0;
124
134
  for (const { table, column } of TEXT_SCAN_COLUMNS) {
125
135
  if (!tableExists(db, table)) continue;
126
- const rows = db.prepare(`SELECT rowid AS rowid, ${column} AS value FROM ${table} WHERE ${column} IS NOT NULL`).all() as Array<{ rowid: number; value: string }>;
136
+ const rows = db.prepare(`SELECT rowid AS rowid, ${column} AS value FROM ${table} WHERE ${column} IS NOT NULL`).all() as Array<{
137
+ rowid: number;
138
+ value: string;
139
+ }>;
127
140
  const updateStmt = db.prepare(`UPDATE ${table} SET ${column} = ? WHERE rowid = ?`);
128
141
  for (const row of rows) {
129
142
  let value = row.value;
package/src/index.ts CHANGED
@@ -4,30 +4,35 @@
4
4
  * than a blanket `export *` on service-heavy modules -- matches ports-and-adapters practice of a
5
5
  * purposeful API, not "everything happens to be reachable."
6
6
  */
7
- export * from "./constants.ts";
8
7
 
8
+ export { projectArtifactRelationships } from "./artifact-relationship-view.ts";
9
+ export {
10
+ connectPapyrusClient,
11
+ type PapyrusClient,
12
+ type PushChannelTarget,
13
+ resolvePushChannelTarget,
14
+ resolveVehicleClientTarget,
15
+ type VehicleClientTarget,
16
+ } from "./client.ts";
17
+ export * from "./constants.ts";
18
+ export type { DiscussionAndRounds } from "./discussion-service.ts";
9
19
  export type { Artifact, ArtifactEdge } from "./domain/artifact.ts";
10
- export { PROOF_TYPES, checklistEntries, type ProofReference } from "./domain/checklist.ts";
11
- export { DISCUSSION_SUBTYPE, readDiscussionExtra, type DiscussionRound } from "./domain/discussion.ts";
20
+ export { checklistEntries, PROOF_TYPES, type ProofReference } from "./domain/checklist.ts";
21
+ export { DISCUSSION_SUBTYPE, type DiscussionRound, readDiscussionExtra } from "./domain/discussion.ts";
12
22
  export type { DisplayGraph, DisplayGraphEdge, DisplayGraphNode, RenderedGraph } from "./domain/display-graph.ts";
13
23
  export type { GateResult } from "./domain/gate.ts";
14
24
  export type { NoteHistoryPage } from "./domain/note-event.ts";
15
25
  export type { TaskEvent, TaskHistoryPage } from "./domain/task-event.ts";
16
26
  export type { TaskLease } from "./domain/task-lease.ts";
17
27
  export type { TaskViewSelection } from "./domain/task-scope.ts";
18
-
28
+ export { NOTE_DISPOSITIONS } from "./note-service.ts";
29
+ export type { PlaybookInvocationResult, PlaybookMissingArguments } from "./playbook-execution.ts";
19
30
  export type { ArtifactStore } from "./ports/artifact-store.ts";
20
31
  export type { GraphRenderer } from "./ports/graph-renderer.ts";
21
-
22
- export { connectPapyrusClient, resolvePushChannelTarget, resolveVehicleClientTarget, type PapyrusClient, type PushChannelTarget, type VehicleClientTarget } from "./client.ts";
23
- export type { DiscussionAndRounds } from "./discussion-service.ts";
24
- export { NOTE_DISPOSITIONS } from "./note-service.ts";
25
32
  export type { OperationName, SchemaState } from "./service.ts";
26
- export type { WorkflowRunResult } from "./workflow-execution.ts";
27
- export type { PlaybookInvocationResult, PlaybookMissingArguments } from "./playbook-execution.ts";
28
- export { projectArtifactRelationships } from "./artifact-relationship-view.ts";
29
33
  export { taskContext } from "./task-context.ts";
30
34
  export { projectTaskExecution, type TaskExecutionPlan, type TaskExecutionState } from "./task-execution.ts";
31
35
  export { projectTaskGraph, type TaskGraphView } from "./task-graph-view.ts";
32
36
  export { fallbackLabel, projectTaskRelationships } from "./task-relationship-view.ts";
33
37
  export type { TaskCompletion, TaskGraph, TaskNode, TaskStatus } from "./task-service.ts";
38
+ export type { WorkflowRunResult } from "./workflow-execution.ts";
@@ -1,14 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import {
3
+ type AppendLogEntryCommand,
4
+ type AppendLogEntryResult,
3
5
  boundMessage,
4
6
  LOG_QUERY_MAX_ENTRIES,
5
7
  LOG_RETENTION_MAX_ENTRIES_PER_SOURCE,
6
- meetsLevel,
7
- validateAppendLogEntryCommand,
8
- type AppendLogEntryCommand,
9
- type AppendLogEntryResult,
10
8
  type LogEntryPage,
11
9
  type LogQuery,
10
+ meetsLevel,
11
+ validateAppendLogEntryCommand,
12
12
  } from "./domain/log-entry.ts";
13
13
  import type { LogStore } from "./ports/log-store.ts";
14
14
 
@@ -47,7 +47,8 @@ export class Logs {
47
47
  */
48
48
  query(query: LogQuery): LogEntryPage {
49
49
  const limit = Math.min(query.limit ?? LOG_QUERY_MAX_ENTRIES, LOG_QUERY_MAX_ENTRIES);
50
- const matching = this.store.entriesForSource(query.sourceId)
50
+ const matching = this.store
51
+ .entriesForSource(query.sourceId)
51
52
  .filter((entry) => query.since === undefined || entry.occurredAt > query.since)
52
53
  .filter((entry) => query.level === undefined || meetsLevel(entry.level, query.level));
53
54
 
package/src/log.ts CHANGED
@@ -1,6 +1,25 @@
1
+ import type { Logger } from "@danypops/vehicle-server/logging";
2
+
1
3
  export type LogLevel = "info" | "warn" | "error";
2
4
 
3
5
  /** Credential-safe structured daemon event. Callers must pass bounded, non-sensitive fields. */
4
6
  export function logEvent(level: LogLevel, event: string, fields: Record<string, unknown> = {}): void {
5
7
  console.error(JSON.stringify({ timestamp: new Date().toISOString(), level, component: "papyrus-daemon", event, ...fields }));
6
8
  }
9
+
10
+ /**
11
+ * Adapts logEvent to @danypops/vehicle-server's Logger interface (debug/
12
+ * info/warn/error), so createVehicleHttpApp's own failure logging lands
13
+ * through this daemon's one existing structured-log sink instead of
14
+ * introducing a second logging system. debug is a no-op -- this daemon's
15
+ * own logEvent never had a debug level, and none of its existing output
16
+ * needs one.
17
+ */
18
+ export function vehicleLogger(): Logger {
19
+ return {
20
+ debug() {},
21
+ info: (msg, fields) => logEvent("info", msg, fields),
22
+ warn: (msg, fields) => logEvent("warn", msg, fields),
23
+ error: (msg, fields) => logEvent("error", msg, fields),
24
+ };
25
+ }
@@ -2,8 +2,9 @@
2
2
  * modules/discuss.ts — Discuss as a Papyrus-native registered module. See
3
3
  * domain/discussion.ts and discussion-service.ts for the full design.
4
4
  */
5
- import type { ArtifactEventContext } from "../domain/artifact-event.ts";
5
+
6
6
  import type { Discussions } from "../discussion-service.ts";
7
+ import type { ArtifactEventContext } from "../domain/artifact-event.ts";
7
8
  import type { OperationDefinition } from "../module-registry.ts";
8
9
 
9
10
  const MODULE_ID = "discuss";
@@ -12,7 +13,8 @@ type OperationInput = Record<string, unknown>;
12
13
 
13
14
  function string(input: OperationInput, key: string): string {
14
15
  const value = input[key];
15
- if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required${REQUIRED_FIELD_HINTS[key] ? ` (${REQUIRED_FIELD_HINTS[key]})` : ""}`);
16
+ if (typeof value !== "string" || value.length === 0)
17
+ throw new Error(`${key} is required${REQUIRED_FIELD_HINTS[key] ? ` (${REQUIRED_FIELD_HINTS[key]})` : ""}`);
16
18
  return value;
17
19
  }
18
20
 
@@ -63,38 +65,63 @@ function optionsMode(input: OperationInput): "single" | "multi" | undefined {
63
65
 
64
66
  /** 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. */
65
67
  export const DISCUSS_OPERATION_NAMES = [
66
- "discuss.open", "discuss.reply", "discuss.defer", "discuss.resume", "discuss.settle",
67
- "discuss.block", "discuss.unblock", "discuss.show", "discuss.rounds", "discuss.list",
68
+ "discuss.open",
69
+ "discuss.reply",
70
+ "discuss.defer",
71
+ "discuss.resume",
72
+ "discuss.settle",
73
+ "discuss.block",
74
+ "discuss.unblock",
75
+ "discuss.show",
76
+ "discuss.rounds",
77
+ "discuss.list",
68
78
  ] as const;
69
79
 
70
80
  /** Registers every discuss.* operation against one Discussions instance. */
71
81
  export function discussOperations(discussions: Discussions): OperationDefinition[] {
72
82
  const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
73
- name, moduleId: MODULE_ID, execute,
83
+ name,
84
+ moduleId: MODULE_ID,
85
+ execute,
74
86
  });
75
87
  return [
76
- define("discuss.open", (input: OperationInput) => discussions.open({
77
- title: string(input, "title"),
78
- actor: string(input, "actor"),
79
- content: string(input, "content"),
80
- body: optionalString(input, "body"),
81
- labels: optionalStringArray(input, "labels"),
82
- blocksTaskIds: optionalStringArray(input, "blocks_task_ids") ?? optionalStringArray(input, "blocksTaskIds"),
83
- options: optionalStringArray(input, "options"),
84
- optionsMode: optionsMode(input),
85
- optionDescriptions: optionalStringArray(input, "option_descriptions") ?? optionalStringArray(input, "optionDescriptions"),
86
- }, eventContext(input))),
87
- define("discuss.reply", (input: OperationInput) => discussions.reply(string(input, "id"), {
88
- actor: string(input, "actor"),
89
- content: string(input, "content"),
90
- selected: optionalStringArray(input, "selected"),
91
- options: optionalStringArray(input, "options"),
92
- optionsMode: optionsMode(input),
93
- optionDescriptions: optionalStringArray(input, "option_descriptions") ?? optionalStringArray(input, "optionDescriptions"),
94
- }, eventContext(input))),
95
- define("discuss.defer", (input: OperationInput) => discussions.defer(string(input, "id"), optionalString(input, "reason"), eventContext(input))),
88
+ define("discuss.open", (input: OperationInput) =>
89
+ discussions.open(
90
+ {
91
+ title: string(input, "title"),
92
+ actor: string(input, "actor"),
93
+ content: string(input, "content"),
94
+ body: optionalString(input, "body"),
95
+ labels: optionalStringArray(input, "labels"),
96
+ blocksTaskIds: optionalStringArray(input, "blocks_task_ids") ?? optionalStringArray(input, "blocksTaskIds"),
97
+ options: optionalStringArray(input, "options"),
98
+ optionsMode: optionsMode(input),
99
+ optionDescriptions: optionalStringArray(input, "option_descriptions") ?? optionalStringArray(input, "optionDescriptions"),
100
+ },
101
+ eventContext(input),
102
+ ),
103
+ ),
104
+ define("discuss.reply", (input: OperationInput) =>
105
+ discussions.reply(
106
+ string(input, "id"),
107
+ {
108
+ actor: string(input, "actor"),
109
+ content: string(input, "content"),
110
+ selected: optionalStringArray(input, "selected"),
111
+ options: optionalStringArray(input, "options"),
112
+ optionsMode: optionsMode(input),
113
+ optionDescriptions: optionalStringArray(input, "option_descriptions") ?? optionalStringArray(input, "optionDescriptions"),
114
+ },
115
+ eventContext(input),
116
+ ),
117
+ ),
118
+ define("discuss.defer", (input: OperationInput) =>
119
+ discussions.defer(string(input, "id"), optionalString(input, "reason"), eventContext(input)),
120
+ ),
96
121
  define("discuss.resume", (input: OperationInput) => discussions.resume(string(input, "id"), eventContext(input))),
97
- define("discuss.settle", (input: OperationInput) => discussions.settle(string(input, "id"), string(input, "settlement"), eventContext(input))),
122
+ define("discuss.settle", (input: OperationInput) =>
123
+ discussions.settle(string(input, "id"), string(input, "settlement"), eventContext(input)),
124
+ ),
98
125
  define("discuss.block", (input: OperationInput) => {
99
126
  discussions.block(string(input, "id"), taskId(input), eventContext(input));
100
127
  return { blocked: true };
@@ -103,7 +130,15 @@ export function discussOperations(discussions: Discussions): OperationDefinition
103
130
  unblocked: discussions.unblock(string(input, "id"), taskId(input), eventContext(input)),
104
131
  })),
105
132
  define("discuss.show", (input: OperationInput) => discussions.show(string(input, "id"))),
106
- define("discuss.rounds", (input: OperationInput) => discussions.listRounds(string(input, "id"), optionalNumber(input, "after_round") ?? optionalNumber(input, "afterRound"), optionalNumber(input, "limit"))),
107
- define("discuss.list", (input: OperationInput) => discussions.list({ state: optionalString(input, "state"), limit: optionalNumber(input, "limit") })),
133
+ define("discuss.rounds", (input: OperationInput) =>
134
+ discussions.listRounds(
135
+ string(input, "id"),
136
+ optionalNumber(input, "after_round") ?? optionalNumber(input, "afterRound"),
137
+ optionalNumber(input, "limit"),
138
+ ),
139
+ ),
140
+ define("discuss.list", (input: OperationInput) =>
141
+ discussions.list({ state: optionalString(input, "state"), limit: optionalNumber(input, "limit") }),
142
+ ),
108
143
  ];
109
144
  }
@@ -7,7 +7,16 @@
7
7
  * ArtifactStore-based with no other module's concrete class dependency.
8
8
  */
9
9
  import type { AuthorityRegistry } from "../authority-registry.ts";
10
- import { assignDocumentProject, createDocument, linkDocument, listDocuments, showDocument, transitionDocument, updateDocument, type DocumentRelation } from "../domain-services.ts";
10
+ import {
11
+ assignDocumentProject,
12
+ createDocument,
13
+ type DocumentRelation,
14
+ linkDocument,
15
+ listDocuments,
16
+ showDocument,
17
+ transitionDocument,
18
+ updateDocument,
19
+ } from "../domain-services.ts";
11
20
  import type { OperationDefinition } from "../module-registry.ts";
12
21
  import type { ArtifactScopeStore } from "../ports/artifact-scope-store.ts";
13
22
  import type { ArtifactStore } from "../ports/artifact-store.ts";
@@ -52,29 +61,77 @@ const artifactFilter = (input: OperationInput) => ({
52
61
  /** Registers every docs.* operation against the shared ArtifactStore port. Behavior is unchanged from the prior inline handlers in src/service.ts. */
53
62
  /** 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. */
54
63
  export const DOCS_OPERATION_NAMES = [
55
- "docs.create", "docs.list", "docs.show", "docs.activate", "docs.archive", "docs.reopen", "docs.link", "docs.assign_project", "docs.update",
64
+ "docs.create",
65
+ "docs.list",
66
+ "docs.show",
67
+ "docs.activate",
68
+ "docs.archive",
69
+ "docs.reopen",
70
+ "docs.link",
71
+ "docs.assign_project",
72
+ "docs.update",
56
73
  ] as const;
57
74
 
58
75
  export function docsOperations(artifacts: ArtifactStore, scopes: ArtifactScopeStore, authority: AuthorityRegistry): OperationDefinition[] {
59
76
  const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
60
- name, moduleId: MODULE_ID, execute,
77
+ name,
78
+ moduleId: MODULE_ID,
79
+ execute,
61
80
  });
62
81
  return [
63
- define("docs.create", (input: OperationInput) => createDocument(artifacts, scopes, {
64
- title: string(input, "title"), body: optionalString(input, "body"), subtype: optionalString(input, "subtype"),
65
- labels: input["labels"] as string[] | undefined, extra: input["extra"] as Record<string, unknown> | undefined,
66
- templateId: optionalString(input, "template_id") ?? optionalString(input, "templateId"),
67
- projectRoot: optionalString(input, "project_root"),
68
- }, authority, eventContext(input))),
82
+ define("docs.create", (input: OperationInput) =>
83
+ createDocument(
84
+ artifacts,
85
+ scopes,
86
+ {
87
+ title: string(input, "title"),
88
+ body: optionalString(input, "body"),
89
+ subtype: optionalString(input, "subtype"),
90
+ labels: input.labels as string[] | undefined,
91
+ extra: input.extra as Record<string, unknown> | undefined,
92
+ templateId: optionalString(input, "template_id") ?? optionalString(input, "templateId"),
93
+ projectRoot: optionalString(input, "project_root"),
94
+ },
95
+ authority,
96
+ eventContext(input),
97
+ ),
98
+ ),
69
99
  define("docs.list", (input: OperationInput) => listDocuments(artifacts, scopes, artifactFilter(input))),
70
100
  define("docs.show", (input: OperationInput) => showDocument(artifacts, string(input, "id"))),
71
- define("docs.activate", (input: OperationInput) => transitionDocument(artifacts, string(input, "id"), "activate", authority, eventContext(input))),
72
- define("docs.archive", (input: OperationInput) => transitionDocument(artifacts, string(input, "id"), "archive", authority, eventContext(input))),
73
- define("docs.reopen", (input: OperationInput) => transitionDocument(artifacts, string(input, "id"), "reopen", authority, eventContext(input))),
74
- define("docs.link", (input: OperationInput) => linkDocument(artifacts, string(input, "id"), string(input, "relation") as DocumentRelation, string(input, "target_id"), authority, eventContext(input))),
75
- define("docs.assign_project", (input: OperationInput) => assignDocumentProject(artifacts, scopes, string(input, "id"), optionalString(input, "project_root"))),
76
- define("docs.update", (input: OperationInput) => updateDocument(artifacts, string(input, "id"), {
77
- title: optionalString(input, "title"), body: optionalString(input, "body"), labels: input["labels"] as string[] | undefined,
78
- }, authority, eventContext(input))),
101
+ define("docs.activate", (input: OperationInput) =>
102
+ transitionDocument(artifacts, string(input, "id"), "activate", authority, eventContext(input)),
103
+ ),
104
+ define("docs.archive", (input: OperationInput) =>
105
+ transitionDocument(artifacts, string(input, "id"), "archive", authority, eventContext(input)),
106
+ ),
107
+ define("docs.reopen", (input: OperationInput) =>
108
+ transitionDocument(artifacts, string(input, "id"), "reopen", authority, eventContext(input)),
109
+ ),
110
+ define("docs.link", (input: OperationInput) =>
111
+ linkDocument(
112
+ artifacts,
113
+ string(input, "id"),
114
+ string(input, "relation") as DocumentRelation,
115
+ string(input, "target_id"),
116
+ authority,
117
+ eventContext(input),
118
+ ),
119
+ ),
120
+ define("docs.assign_project", (input: OperationInput) =>
121
+ assignDocumentProject(artifacts, scopes, string(input, "id"), optionalString(input, "project_root")),
122
+ ),
123
+ define("docs.update", (input: OperationInput) =>
124
+ updateDocument(
125
+ artifacts,
126
+ string(input, "id"),
127
+ {
128
+ title: optionalString(input, "title"),
129
+ body: optionalString(input, "body"),
130
+ labels: input.labels as string[] | undefined,
131
+ },
132
+ authority,
133
+ eventContext(input),
134
+ ),
135
+ ),
79
136
  ];
80
137
  }
@@ -6,7 +6,12 @@
6
6
  * itself; this file only parses/validates raw operation input into that typed shape.
7
7
  */
8
8
  import type { AuthorityRegistry } from "../authority-registry.ts";
9
- import { GRAPH_PROJECTION_SCHEMA_VERSION, type GraphProjectionBatch, type ProjectedArtifact, type ProjectedEdge } from "../domain/graph-projection.ts";
9
+ import type {
10
+ GRAPH_PROJECTION_SCHEMA_VERSION,
11
+ GraphProjectionBatch,
12
+ ProjectedArtifact,
13
+ ProjectedEdge,
14
+ } from "../domain/graph-projection.ts";
10
15
  import { GraphProjection } from "../graph-projection-service.ts";
11
16
  import type { OperationDefinition } from "../module-registry.ts";
12
17
  import type { ArtifactStore } from "../ports/artifact-store.ts";
@@ -37,11 +42,11 @@ function parseArtifact(raw: unknown, index: number): ProjectedArtifact {
37
42
  return {
38
43
  externalId: string(raw, "external_id"),
39
44
  kind: string(raw, "kind"),
40
- ...(typeof raw["subtype"] === "string" ? { subtype: raw["subtype"] } : {}),
45
+ ...(typeof raw.subtype === "string" ? { subtype: raw.subtype } : {}),
41
46
  title: string(raw, "title"),
42
- ...(typeof raw["body"] === "string" ? { body: raw["body"] } : {}),
43
- ...(Array.isArray(raw["labels"]) ? { labels: raw["labels"] as string[] } : {}),
44
- ...(isRecord(raw["extra"]) ? { extra: raw["extra"] } : {}),
47
+ ...(typeof raw.body === "string" ? { body: raw.body } : {}),
48
+ ...(Array.isArray(raw.labels) ? { labels: raw.labels as string[] } : {}),
49
+ ...(isRecord(raw.extra) ? { extra: raw.extra } : {}),
45
50
  };
46
51
  }
47
52
 
@@ -52,8 +57,8 @@ function parseEdge(raw: unknown, index: number): ProjectedEdge {
52
57
 
53
58
  function parseBatch(input: OperationInput): GraphProjectionBatch {
54
59
  const schemaVersion = string(input, "schema_version");
55
- const rawArtifacts = input["artifacts"];
56
- const rawEdges = input["edges"] ?? [];
60
+ const rawArtifacts = input.artifacts;
61
+ const rawEdges = input.edges ?? [];
57
62
  if (!Array.isArray(rawArtifacts)) throw new Error("artifacts must be an array");
58
63
  if (!Array.isArray(rawEdges)) throw new Error("edges must be an array");
59
64
  return {
@@ -70,10 +75,16 @@ function parseBatch(input: OperationInput): GraphProjectionBatch {
70
75
  /** 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. */
71
76
  export const GRAPH_PROJECTION_OPERATION_NAMES = ["graph_projection.apply", "graph_projection.checkpoint"] as const;
72
77
 
73
- export function graphProjectionOperations(artifacts: ArtifactStore, store: GraphProjectionStore, authority: AuthorityRegistry): OperationDefinition[] {
78
+ export function graphProjectionOperations(
79
+ artifacts: ArtifactStore,
80
+ store: GraphProjectionStore,
81
+ authority: AuthorityRegistry,
82
+ ): OperationDefinition[] {
74
83
  const projection = new GraphProjection(artifacts, store, authority);
75
84
  const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
76
- name, moduleId: MODULE_ID, execute,
85
+ name,
86
+ moduleId: MODULE_ID,
87
+ execute,
77
88
  });
78
89
  return [
79
90
  define("graph_projection.apply", (input: OperationInput) => projection.apply(parseBatch(input))),
@@ -6,8 +6,8 @@
6
6
  * comment on why `log` is not an Artifact kind).
7
7
  */
8
8
  import type { JsonValue, LogLevel } from "../domain/log-entry.ts";
9
- import type { OperationDefinition } from "../module-registry.ts";
10
9
  import type { Logs } from "../log-service.ts";
10
+ import type { OperationDefinition } from "../module-registry.ts";
11
11
 
12
12
  const MODULE_ID = "logs";
13
13
 
@@ -34,8 +34,14 @@ function optionalNumber(input: OperationInput, key: string): number | undefined
34
34
  }
35
35
 
36
36
  function isJsonValue(value: unknown): value is JsonValue {
37
- return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean"
38
- || Array.isArray(value) || (typeof value === "object");
37
+ return (
38
+ value === null ||
39
+ typeof value === "string" ||
40
+ typeof value === "number" ||
41
+ typeof value === "boolean" ||
42
+ Array.isArray(value) ||
43
+ typeof value === "object"
44
+ );
39
45
  }
40
46
 
41
47
  function optionalFields(input: OperationInput, key: string): JsonValue | undefined {
@@ -51,25 +57,31 @@ export const LOGS_OPERATION_NAMES = ["logs.append", "logs.query"] as const;
51
57
  /** Registers every logs.* operation against one Logs instance. */
52
58
  export function logsOperations(logs: Logs): OperationDefinition[] {
53
59
  const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
54
- name, moduleId: MODULE_ID, execute,
60
+ name,
61
+ moduleId: MODULE_ID,
62
+ execute,
55
63
  });
56
64
  return [
57
- define("logs.append", (input: OperationInput) => logs.append({
58
- sourceId: string(input, "source_id"),
59
- sourceLabel: optionalString(input, "source_label"),
60
- projectRoot: optionalString(input, "project_root") ?? null,
61
- level: string(input, "level") as LogLevel,
62
- message: string(input, "message"),
63
- fields: optionalFields(input, "fields"),
64
- operationId: string(input, "operation_id"),
65
- sessionId: optionalString(input, "session_id"),
66
- occurredAt: optionalString(input, "occurred_at"),
67
- })),
68
- define("logs.query", (input: OperationInput) => logs.query({
69
- sourceId: string(input, "source_id"),
70
- since: optionalString(input, "since"),
71
- level: optionalString(input, "level") as LogLevel | undefined,
72
- limit: optionalNumber(input, "limit"),
73
- })),
65
+ define("logs.append", (input: OperationInput) =>
66
+ logs.append({
67
+ sourceId: string(input, "source_id"),
68
+ sourceLabel: optionalString(input, "source_label"),
69
+ projectRoot: optionalString(input, "project_root") ?? null,
70
+ level: string(input, "level") as LogLevel,
71
+ message: string(input, "message"),
72
+ fields: optionalFields(input, "fields"),
73
+ operationId: string(input, "operation_id"),
74
+ sessionId: optionalString(input, "session_id"),
75
+ occurredAt: optionalString(input, "occurred_at"),
76
+ }),
77
+ ),
78
+ define("logs.query", (input: OperationInput) =>
79
+ logs.query({
80
+ sourceId: string(input, "source_id"),
81
+ since: optionalString(input, "since"),
82
+ level: optionalString(input, "level") as LogLevel | undefined,
83
+ limit: optionalNumber(input, "limit"),
84
+ }),
85
+ ),
74
86
  ];
75
87
  }