@danypops/papyrus 0.41.0 → 0.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -11
- package/package.json +2 -2
- package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
- package/src/adapters/sqlite-artifact-store.ts +7 -5
- package/src/adapters/sqlite-discussion-round-store.ts +26 -17
- package/src/adapters/sqlite-gate-runner.ts +1 -1
- package/src/adapters/sqlite-graph-projection-store.ts +14 -10
- package/src/adapters/sqlite-log-store.ts +36 -17
- package/src/adapters/sqlite-note-event-store.ts +20 -16
- package/src/adapters/sqlite-session-identity-store.ts +13 -7
- package/src/adapters/sqlite-task-event-store.ts +29 -21
- package/src/adapters/sqlite-task-focus-store.ts +36 -10
- package/src/adapters/sqlite-task-lease-store.ts +12 -6
- package/src/adapters/sqlite-task-scope-store.ts +25 -14
- package/src/artifact-relationship-view.ts +4 -4
- package/src/artifact-subtree.ts +4 -2
- package/src/authority-registry.ts +2 -1
- package/src/cli.ts +794 -354
- package/src/client.ts +6 -3
- package/src/constants.ts +34 -56
- package/src/daemon-state.ts +4 -12
- package/src/daemon.ts +31 -9
- package/src/db.ts +153 -105
- package/src/discussion-service.ts +109 -44
- package/src/domain/artifact-event.ts +18 -5
- package/src/domain/artifact.ts +3 -1
- package/src/domain/blueprint-definition.ts +268 -0
- package/src/domain/checklist.ts +20 -17
- package/src/domain/discussion.ts +37 -18
- package/src/domain/gate.ts +7 -7
- package/src/domain/log-entry.ts +1 -1
- package/src/domain/note-event.ts +20 -7
- package/src/domain/task-event.ts +17 -7
- package/src/domain-services.ts +362 -288
- package/src/graph-projection-service.ts +34 -8
- package/src/id-migration.ts +17 -4
- package/src/index.ts +16 -11
- package/src/log-service.ts +6 -5
- package/src/log.ts +19 -0
- package/src/modules/discuss.ts +63 -28
- package/src/modules/docs.ts +74 -17
- package/src/modules/graph-projection.ts +20 -9
- package/src/modules/logs.ts +34 -22
- package/src/modules/notes.ts +66 -28
- package/src/modules/playbooks.ts +93 -32
- package/src/modules/rules.ts +57 -15
- package/src/modules/session-identity.ts +6 -2
- package/src/modules/tasks.ts +142 -67
- package/src/note-service.ts +11 -7
- package/src/ops.ts +134 -69
- package/src/playbook-definition.ts +124 -39
- package/src/playbook-execution.ts +18 -25
- package/src/ports/artifact-scope-store.ts +1 -1
- package/src/ports/note-event-store.ts +6 -4
- package/src/ports/task-event-store.ts +9 -6
- package/src/ports/task-focus-store.ts +17 -4
- package/src/ports/task-lease-store.ts +9 -4
- package/src/ports/task-scope-store.ts +3 -1
- package/src/service.ts +148 -110
- package/src/session-identity-service.ts +10 -2
- package/src/task-context.ts +28 -16
- package/src/task-execution.ts +4 -12
- package/src/task-graph-view.ts +12 -12
- package/src/task-relationship-view.ts +1 -3
- package/src/task-service.ts +168 -73
- package/src/vehicle/artifact-trash-vehicle.ts +26 -14
- package/src/vehicle/artifact-vehicle-shared.ts +32 -13
- package/src/vehicle/docs-vehicle.ts +50 -18
- package/src/vehicle/notes-vehicle.ts +26 -8
- package/src/vehicle/papyrus-vehicle.ts +16 -8
- package/src/vehicle/playbooks-vehicle.ts +88 -19
- package/src/vehicle/rules-vehicle.ts +58 -21
- package/src/vehicle/tasks-vehicle.ts +366 -54
- package/src/version.ts +1 -1
- package/src/workflow-execution.ts +198 -109
- package/src/domain/skill-definition.ts +0 -270
- package/src/modules/skills.ts +0 -158
- package/src/vehicle/skills-vehicle.ts +0 -194
|
@@ -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
|
-
|
|
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(
|
|
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) {
|
|
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 {
|
|
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(
|
|
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(
|
|
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, {
|
|
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({
|
package/src/id-migration.ts
CHANGED
|
@@ -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 = [
|
|
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
|
|
106
|
-
|
|
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<{
|
|
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 {
|
|
11
|
-
export { DISCUSSION_SUBTYPE,
|
|
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";
|
package/src/log-service.ts
CHANGED
|
@@ -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
|
|
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
|
+
}
|
package/src/modules/discuss.ts
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
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",
|
|
67
|
-
"discuss.
|
|
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,
|
|
83
|
+
name,
|
|
84
|
+
moduleId: MODULE_ID,
|
|
85
|
+
execute,
|
|
74
86
|
});
|
|
75
87
|
return [
|
|
76
|
-
define("discuss.open", (input: OperationInput) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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) =>
|
|
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) =>
|
|
107
|
-
|
|
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
|
}
|
package/src/modules/docs.ts
CHANGED
|
@@ -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 {
|
|
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",
|
|
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,
|
|
77
|
+
name,
|
|
78
|
+
moduleId: MODULE_ID,
|
|
79
|
+
execute,
|
|
61
80
|
});
|
|
62
81
|
return [
|
|
63
|
-
define("docs.create", (input: OperationInput) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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) =>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
define("docs.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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
|
|
45
|
+
...(typeof raw.subtype === "string" ? { subtype: raw.subtype } : {}),
|
|
41
46
|
title: string(raw, "title"),
|
|
42
|
-
...(typeof raw
|
|
43
|
-
...(Array.isArray(raw
|
|
44
|
-
...(isRecord(raw
|
|
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
|
|
56
|
-
const rawEdges = input
|
|
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(
|
|
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,
|
|
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))),
|
package/src/modules/logs.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* modules/logs.ts — the `log` domain as a registered Papyrus-native module.
|
|
3
3
|
*
|
|
4
|
-
* Deliberately self-contained: does not import artifact/task/rule/
|
|
4
|
+
* Deliberately self-contained: does not import artifact/task/rule/playbook infrastructure --
|
|
5
5
|
* logs never touch the Artifact graph directly (see src/domain/log-entry.ts's own module
|
|
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
|
|
38
|
-
|
|
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,
|
|
60
|
+
name,
|
|
61
|
+
moduleId: MODULE_ID,
|
|
62
|
+
execute,
|
|
55
63
|
});
|
|
56
64
|
return [
|
|
57
|
-
define("logs.append", (input: OperationInput) =>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
}
|