@danypops/papyrus 0.57.1 → 0.58.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/package.json +1 -1
- package/src/artifact/artifact-scope-store.ts +13 -17
- package/src/artifact/in-memory-artifact-scope-store.ts +8 -10
- package/src/artifact/sqlite-artifact-scope-store.ts +8 -8
- package/src/docs/docs-service.ts +1 -1
- package/src/domain-service-shared.ts +1 -1
- package/src/gate/gate-execution.ts +4 -17
- package/src/handlers/playbooks.ts +4 -4
- package/src/handlers/registry.ts +2 -2
- package/src/handlers/task-classifiers.ts +1 -9
- package/src/handlers/tasks.ts +1 -1
- package/src/index.ts +2 -2
- package/src/modules/playbooks.ts +4 -4
- package/src/modules/projects.ts +1 -1
- package/src/modules/tasks.ts +2 -2
- package/src/playbook/playbook-service.ts +1 -1
- package/src/playbook/workflow-execution.ts +6 -6
- package/src/project-registry/scope-source.ts +14 -0
- package/src/rules/rules-service.ts +1 -1
- package/src/scope-group/scope-group.ts +4 -11
- package/src/service.ts +6 -6
- package/src/{task-event → task/event}/sqlite-task-event-store.ts +4 -4
- package/src/{task-event → task/event}/task-event-store.ts +6 -2
- package/src/{task-event → task/event}/task-event.ts +1 -1
- package/src/{task-scope → task/scope}/sqlite-task-scope-store.ts +5 -5
- package/src/{task-scope → task/scope}/task-scope-store.ts +9 -11
- package/src/{task-scope → task/scope}/task-scope.ts +8 -13
- package/src/task/task-edges.ts +6 -13
- package/src/task/task-focus-coordinator.ts +4 -10
- package/src/task/task-lease-coordinator.ts +3 -6
- package/src/task/task-mutation-coordinator.ts +9 -19
- package/src/task/task-project-scope.ts +6 -11
- package/src/task/task-service.ts +19 -19
package/package.json
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
+
import type { ScopeAssignmentSource } from "../project-registry/scope-source.ts";
|
|
1
2
|
import type { ScopeMemberRef } from "../scope-group/scope-group.ts";
|
|
2
|
-
import type { TaskScopeSource } from "../task-scope/task-scope.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Project scoping for Docs/Rules/Playbooks: an artifact
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* an
|
|
9
|
-
*
|
|
10
|
-
* shared ProjectRegistryStore) or a scope group (from ScopeGroupStore, itself a possibly-nested
|
|
11
|
-
* collection of projects/groups) -- "explicit scope can include nested scopes". Membership is by
|
|
12
|
-
* id, never by root/name, so a registered project's root (or a group's name) can move without a
|
|
13
|
-
* best-effort string rewrite across every artifact that references it.
|
|
5
|
+
* Project scoping for Docs/Rules/Playbooks: "none" hides an artifact from context injection
|
|
6
|
+
* entirely, "all" applies it everywhere, "explicit" binds it to a non-empty member set (a
|
|
7
|
+
* project or a possibly-nested scope group). `mode` is its own field rather than inferred from
|
|
8
|
+
* an empty member list, and membership is by id rather than root/name, so a project or group can
|
|
9
|
+
* be renamed/moved without rewriting every artifact that references it.
|
|
14
10
|
*/
|
|
15
11
|
export type ArtifactScopeMode = "none" | "all" | "explicit";
|
|
16
12
|
|
|
@@ -19,13 +15,13 @@ export interface ArtifactScope {
|
|
|
19
15
|
mode: ArtifactScopeMode;
|
|
20
16
|
/** Direct membership only (not expanded through nested groups) -- always empty when mode is "none"/"all", always non-empty when mode is "explicit". */
|
|
21
17
|
members: ScopeMemberRef[];
|
|
22
|
-
source:
|
|
18
|
+
source: ScopeAssignmentSource;
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
export interface LegacyArtifactScope {
|
|
26
22
|
artifactId: string;
|
|
27
23
|
projectRoot?: string;
|
|
28
|
-
source:
|
|
24
|
+
source: ScopeAssignmentSource;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
export interface ArtifactScopeStore {
|
|
@@ -34,15 +30,15 @@ export interface ArtifactScopeStore {
|
|
|
34
30
|
/** Single-root compatibility view over scope(): an "all"/"none" or unscoped artifact omits projectRoot; an "explicit" mode artifact with exactly one project-type membership (and no group members) resolves it back to that project's current root; anything else (zero, more than one, or any group membership) omits projectRoot, since this shape cannot represent it. */
|
|
35
31
|
get(artifactId: string): LegacyArtifactScope | undefined;
|
|
36
32
|
/** Single-root compatibility shim: registers/resolves projectRoot and replaces the artifact's scope with exactly that one project membership, or setAll() when projectRoot is undefined. Every existing caller (rules/docs/playbooks assign_project) keeps working unchanged. */
|
|
37
|
-
assign(artifactId: string, projectRoot: string | undefined, source:
|
|
33
|
+
assign(artifactId: string, projectRoot: string | undefined, source: ScopeAssignmentSource): LegacyArtifactScope;
|
|
38
34
|
/** Sets an artifact to explicitly "all" (applies everywhere), clearing any membership. */
|
|
39
|
-
setAll(artifactId: string, source:
|
|
35
|
+
setAll(artifactId: string, source: ScopeAssignmentSource): ArtifactScope;
|
|
40
36
|
/** Sets an artifact to explicitly "none" (fully hidden -- never applicable, never context-injected, regardless of project), clearing any membership. */
|
|
41
|
-
setNone(artifactId: string, source:
|
|
37
|
+
setNone(artifactId: string, source: ScopeAssignmentSource): ArtifactScope;
|
|
42
38
|
/** Replaces an artifact's entire explicit membership set with exactly these (project and/or group) members -- must be non-empty; use setAll/setNone to clear scoping entirely. */
|
|
43
|
-
replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source:
|
|
39
|
+
replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: ScopeAssignmentSource): ArtifactScope;
|
|
44
40
|
/** Adds one member to an artifact's explicit membership (idempotent), switching mode to "explicit" if it was "all"/"none". Enforces the bounded maximum membership count. */
|
|
45
|
-
addMember(artifactId: string, member: ScopeMemberRef, source:
|
|
41
|
+
addMember(artifactId: string, member: ScopeMemberRef, source: ScopeAssignmentSource): ArtifactScope;
|
|
46
42
|
/** Removes one member from an artifact's explicit membership (idempotent). Rejects removing the last membership while mode is "explicit": a caller must explicitly call setAll/setNone instead of accidentally broadening/narrowing scope by emptying the set. */
|
|
47
43
|
removeMember(artifactId: string, member: ScopeMemberRef): ArtifactScope;
|
|
48
44
|
/** Bounded id listing for one project root's EXACT direct membership (or the "all" bucket when projectRoot is undefined) -- an unregistered root always yields an empty list. Deliberately does not expand nested groups (audit semantics, matching this method's own pre-existing "exact membership" contract) -- use appliesToProjectRoot for the applicable/injection query instead. */
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT } from "../constants.ts";
|
|
2
2
|
import { InMemoryProjectRegistryStore } from "../project-registry/in-memory-project-registry-store.ts";
|
|
3
3
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
4
|
+
import type { ScopeAssignmentSource } from "../project-registry/scope-source.ts";
|
|
4
5
|
import { InMemoryScopeGroupStore } from "../scope-group/in-memory-scope-group-store.ts";
|
|
5
6
|
import { type ScopeMemberRef, sameScopeMember } from "../scope-group/scope-group.ts";
|
|
6
7
|
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
7
|
-
import type { TaskScopeSource } from "../task-scope/task-scope.ts";
|
|
8
8
|
import type { ArtifactScope, ArtifactScopeMode, ArtifactScopeStore, LegacyArtifactScope } from "./artifact-scope-store.ts";
|
|
9
9
|
|
|
10
10
|
interface Row {
|
|
11
11
|
mode: ArtifactScopeMode;
|
|
12
12
|
members: ScopeMemberRef[];
|
|
13
|
-
source:
|
|
13
|
+
source: ScopeAssignmentSource;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
|
|
@@ -18,9 +18,7 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
|
|
|
18
18
|
private readonly registry: InMemoryProjectRegistryStore;
|
|
19
19
|
private readonly scopeGroups: ScopeGroupStore;
|
|
20
20
|
|
|
21
|
-
// Membership is
|
|
22
|
-
// ProjectRegistryStore.registerProject) or a scope group rename needs no rewrite here at all --
|
|
23
|
-
// unlike InMemoryTaskScopeStore, this store never subscribes to root-move notifications.
|
|
21
|
+
// Membership is by project/group id, so a registry root move or group rename needs no rewrite here.
|
|
24
22
|
constructor(registry?: ProjectRegistryStore, scopeGroups?: ScopeGroupStore) {
|
|
25
23
|
this.registry = registry instanceof InMemoryProjectRegistryStore ? registry : new InMemoryProjectRegistryStore();
|
|
26
24
|
this.scopeGroups = scopeGroups ?? new InMemoryScopeGroupStore();
|
|
@@ -45,7 +43,7 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
|
|
|
45
43
|
return { artifactId, ...(projectRoot === undefined ? {} : { projectRoot }), source: row.source };
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
assign(artifactId: string, projectRoot: string | undefined, source:
|
|
46
|
+
assign(artifactId: string, projectRoot: string | undefined, source: ScopeAssignmentSource): LegacyArtifactScope {
|
|
49
47
|
if (projectRoot === undefined) {
|
|
50
48
|
this.setAll(artifactId, source);
|
|
51
49
|
return { artifactId, source };
|
|
@@ -55,19 +53,19 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
|
|
|
55
53
|
return { artifactId, projectRoot: project.projectRoot, source };
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
setAll(artifactId: string, source:
|
|
56
|
+
setAll(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
|
|
59
57
|
const row: Row = { mode: "all", members: [], source };
|
|
60
58
|
this.rows.set(artifactId, row);
|
|
61
59
|
return this.toScope(artifactId, row);
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
setNone(artifactId: string, source:
|
|
62
|
+
setNone(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
|
|
65
63
|
const row: Row = { mode: "none", members: [], source };
|
|
66
64
|
this.rows.set(artifactId, row);
|
|
67
65
|
return this.toScope(artifactId, row);
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source:
|
|
68
|
+
replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: ScopeAssignmentSource): ArtifactScope {
|
|
71
69
|
if (members.length === 0) throw new Error("replaceMembers requires at least one member; use setAll/setNone to clear scoping");
|
|
72
70
|
if (members.length > ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
|
|
73
71
|
throw new Error(`an artifact cannot have more than ${ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT} scope members`);
|
|
@@ -78,7 +76,7 @@ export class InMemoryArtifactScopeStore implements ArtifactScopeStore {
|
|
|
78
76
|
return this.toScope(artifactId, row);
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
addMember(artifactId: string, member: ScopeMemberRef, source:
|
|
79
|
+
addMember(artifactId: string, member: ScopeMemberRef, source: ScopeAssignmentSource): ArtifactScope {
|
|
82
80
|
const existing = this.rows.get(artifactId);
|
|
83
81
|
const members = dedupeMembers(existing?.mode === "explicit" ? existing.members : []);
|
|
84
82
|
if (!members.some((candidate) => sameScopeMember(candidate, member)) && members.length >= ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT } from "../constants.ts";
|
|
2
2
|
import type { Db } from "../db.ts";
|
|
3
3
|
import { inTransaction } from "../db.ts";
|
|
4
|
+
import type { ScopeAssignmentSource } from "../project-registry/scope-source.ts";
|
|
4
5
|
import { SQLiteProjectRegistryStore } from "../project-registry/sqlite-project-registry-store.ts";
|
|
5
6
|
import type { ScopeMemberRef } from "../scope-group/scope-group.ts";
|
|
6
7
|
import { SQLiteScopeGroupStore } from "../scope-group/sqlite-scope-group-store.ts";
|
|
7
|
-
import type { TaskScopeSource } from "../task-scope/task-scope.ts";
|
|
8
8
|
import type { ArtifactScope, ArtifactScopeMode, ArtifactScopeStore, LegacyArtifactScope } from "./artifact-scope-store.ts";
|
|
9
9
|
|
|
10
10
|
interface ScopeRow {
|
|
11
11
|
artifact_id: string;
|
|
12
12
|
mode: ArtifactScopeMode;
|
|
13
|
-
source:
|
|
13
|
+
source: ScopeAssignmentSource;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
interface MemberRow {
|
|
@@ -65,7 +65,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
|
|
|
65
65
|
return { artifactId, ...(project ? { projectRoot: project.projectRoot } : {}), source: row.source };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
assign(artifactId: string, projectRoot: string | undefined, source:
|
|
68
|
+
assign(artifactId: string, projectRoot: string | undefined, source: ScopeAssignmentSource): LegacyArtifactScope {
|
|
69
69
|
if (projectRoot === undefined) {
|
|
70
70
|
this.setAll(artifactId, source);
|
|
71
71
|
return { artifactId, source };
|
|
@@ -77,7 +77,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
private upsertScopeRow(artifactId: string, mode: ArtifactScopeMode, source:
|
|
80
|
+
private upsertScopeRow(artifactId: string, mode: ArtifactScopeMode, source: ScopeAssignmentSource): void {
|
|
81
81
|
this.db
|
|
82
82
|
.prepare(`
|
|
83
83
|
INSERT INTO artifact_scopes (artifact_id, project_root, mode, source, assigned_at)
|
|
@@ -91,7 +91,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
|
|
|
91
91
|
.run(artifactId, mode, source, new Date().toISOString());
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
setAll(artifactId: string, source:
|
|
94
|
+
setAll(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
|
|
95
95
|
return inTransaction(this.db, () => {
|
|
96
96
|
this.upsertScopeRow(artifactId, "all", source);
|
|
97
97
|
this.db.prepare("DELETE FROM artifact_scope_members WHERE artifact_id = ?").run(artifactId);
|
|
@@ -99,7 +99,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
|
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
setNone(artifactId: string, source:
|
|
102
|
+
setNone(artifactId: string, source: ScopeAssignmentSource): ArtifactScope {
|
|
103
103
|
return inTransaction(this.db, () => {
|
|
104
104
|
this.upsertScopeRow(artifactId, "none", source);
|
|
105
105
|
this.db.prepare("DELETE FROM artifact_scope_members WHERE artifact_id = ?").run(artifactId);
|
|
@@ -107,7 +107,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source:
|
|
110
|
+
replaceMembers(artifactId: string, members: readonly ScopeMemberRef[], source: ScopeAssignmentSource): ArtifactScope {
|
|
111
111
|
if (members.length === 0) throw new Error("replaceMembers requires at least one member; use setAll/setNone to clear scoping");
|
|
112
112
|
if (members.length > ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
|
|
113
113
|
throw new Error(`an artifact cannot have more than ${ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT} scope members`);
|
|
@@ -127,7 +127,7 @@ export class SQLiteArtifactScopeStore implements ArtifactScopeStore {
|
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
addMember(artifactId: string, member: ScopeMemberRef, source:
|
|
130
|
+
addMember(artifactId: string, member: ScopeMemberRef, source: ScopeAssignmentSource): ArtifactScope {
|
|
131
131
|
return inTransaction(this.db, () => {
|
|
132
132
|
const current = this.members(artifactId);
|
|
133
133
|
const already = current.some((candidate) => candidate.type === member.type && candidate.id === member.id);
|
package/src/docs/docs-service.ts
CHANGED
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
} from "../domain-service-shared.ts";
|
|
31
31
|
import { NOTE_SUBTYPE } from "../note/note-service.ts";
|
|
32
32
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
33
|
+
import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
|
|
33
34
|
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
34
|
-
import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
|
|
35
35
|
|
|
36
36
|
function rejectsNoteTemplate(artifacts: ArtifactStore, templateId: string | undefined, subtype: string | undefined): boolean {
|
|
37
37
|
if (subtype === NOTE_SUBTYPE) return true;
|
|
@@ -19,9 +19,9 @@ import {
|
|
|
19
19
|
} from "./constants.ts";
|
|
20
20
|
import { resolveProjectReference } from "./project-registry/project-registry.ts";
|
|
21
21
|
import type { ProjectRegistryStore } from "./project-registry/project-registry-store.ts";
|
|
22
|
+
import { normalizeProjectRoot } from "./project-registry/scope-source.ts";
|
|
22
23
|
import { resolveScopeGroupReference } from "./scope-group/scope-group.ts";
|
|
23
24
|
import type { ScopeGroupStore } from "./scope-group/scope-group-store.ts";
|
|
24
|
-
import { normalizeProjectRoot } from "./task-scope/task-scope.ts";
|
|
25
25
|
|
|
26
26
|
export interface UpdateContentInput {
|
|
27
27
|
title?: string;
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gate-execution engine
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* refactor (sync/async outcome evaluation shared via evaluateProcessGateResult/spawnErrorGateResult)
|
|
7
|
-
* but still lived inside the artifact-CRUD file until now.
|
|
8
|
-
*
|
|
9
|
-
* Only `runGates`/`runGatesAsync` are real public API -- verified via find_references before this
|
|
10
|
-
* move, not assumed from a grep hit count: the only two real importers were
|
|
11
|
-
* stores/sqlite-gate-runner.ts and test/ops.test.ts (every other `runGates`-named hit in the
|
|
12
|
-
* codebase is Tasks.runGates, a same-named but distinct method that calls into this module only
|
|
13
|
-
* indirectly, through the GateRunner port). Both were updated to import from this file directly;
|
|
14
|
-
* no barrel re-export needed.
|
|
15
|
-
*
|
|
16
|
-
* Depends on ops.ts's own `getArtifact` (still the right owner of that read -- it's real
|
|
17
|
-
* artifact-CRUD, not gate-execution's own concern) -- a one-directional dependency, since ops.ts
|
|
18
|
-
* no longer needs to import anything back from here.
|
|
2
|
+
* Gate-execution engine. `Tasks.runGates` is a separate, same-named method that calls into this
|
|
3
|
+
* module only indirectly, through the GateRunner port -- not the same thing as this file's
|
|
4
|
+
* `runGates`/`runGatesAsync`. Depends on ops.ts's `getArtifact` (real artifact-CRUD, not this
|
|
5
|
+
* module's own concern); ops.ts does not depend back on this file.
|
|
19
6
|
*/
|
|
20
7
|
import { createRequire } from "node:module";
|
|
21
8
|
import {
|
|
@@ -27,9 +27,9 @@ import { listPlaybooks } from "../playbook/playbook-service.ts";
|
|
|
27
27
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
28
28
|
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
29
29
|
import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
|
|
30
|
+
import type { TaskEventSink } from "../task/event/task-event-store.ts";
|
|
31
|
+
import type { TaskScopeAssigner } from "../task/scope/task-scope-store.ts";
|
|
30
32
|
import type { Tasks } from "../task/task-service.ts";
|
|
31
|
-
import type { TaskEventStore } from "../task-event/task-event-store.ts";
|
|
32
|
-
import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
|
|
33
33
|
import {
|
|
34
34
|
booleanProp,
|
|
35
35
|
buildWorkflowRunContent,
|
|
@@ -53,8 +53,8 @@ const jsonObjectProp = {
|
|
|
53
53
|
|
|
54
54
|
export interface PlaybooksVehicleDeps {
|
|
55
55
|
artifacts: ArtifactStore;
|
|
56
|
-
events:
|
|
57
|
-
scopes:
|
|
56
|
+
events: TaskEventSink;
|
|
57
|
+
scopes: TaskScopeAssigner;
|
|
58
58
|
artifactScopes: ArtifactScopeStore;
|
|
59
59
|
tasks: Tasks;
|
|
60
60
|
sessionIdentity: SessionIdentity;
|
package/src/handlers/registry.ts
CHANGED
|
@@ -16,9 +16,9 @@ import type { Notes } from "../note/note-service.ts";
|
|
|
16
16
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
17
17
|
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
18
18
|
import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
|
|
19
|
+
import type { TaskEventStore } from "../task/event/task-event-store.ts";
|
|
20
|
+
import type { TaskScopeStore } from "../task/scope/task-scope-store.ts";
|
|
19
21
|
import type { Tasks } from "../task/task-service.ts";
|
|
20
|
-
import type { TaskEventStore } from "../task-event/task-event-store.ts";
|
|
21
|
-
import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
|
|
22
22
|
import { registerArtifactTrashOperations } from "./artifact-trash.ts";
|
|
23
23
|
import { registerDiscussVehicleOperations } from "./discuss.ts";
|
|
24
24
|
import { registerDocsVehicleOperations } from "./docs.ts";
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Business-rule error classifiers -- turn an ordinary, expected domain rejection into its own
|
|
3
|
-
* classified VehicleError instead of vehicle-registry's generic opaque "handler-failed", split out
|
|
4
|
-
* of handlers/shared.ts as part of a SOLID-audit-driven decomposition (see Doc "Modularity
|
|
5
|
-
* playbook: building-block-shaped TypeScript modules for papyrus/pi-papyrus" and the
|
|
6
|
-
* "handlers/shared.ts split" child of "Epic: Modularize papyrus/pi-papyrus god-files into
|
|
7
|
-
* building-block modules"). Unlike operation-schema.ts, every function here is specific to this
|
|
8
|
-
* package's own domain error types.
|
|
9
|
-
*/
|
|
1
|
+
/** Business-rule error classifiers -- turn an ordinary, expected domain rejection into its own classified VehicleError instead of vehicle-registry's generic opaque "handler-failed". */
|
|
10
2
|
import { VehicleError } from "@danypops/vehicle-core";
|
|
11
3
|
import { PlaybookCompositionError } from "../playbook/playbook-definition.ts";
|
|
12
4
|
import { InvalidSessionSecretError } from "../session-identity/session-identity-service.ts";
|
package/src/handlers/tasks.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { tasksOperations } from "../modules/tasks.ts";
|
|
|
28
28
|
import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
|
|
29
29
|
import { PROOF_TYPES } from "../task/checklist.ts";
|
|
30
30
|
import { TaskMutationIdempotencyConflictError, TaskMutationPendingError } from "../task/mutation-request/task-mutation-request-store.ts";
|
|
31
|
+
import type { TaskViewMode } from "../task/scope/task-scope.ts";
|
|
31
32
|
import type { TaskExecutionPlan } from "../task/task-execution.ts";
|
|
32
33
|
import {
|
|
33
34
|
type TaskCompletion,
|
|
@@ -37,7 +38,6 @@ import {
|
|
|
37
38
|
TaskProjectNotFoundError,
|
|
38
39
|
type Tasks,
|
|
39
40
|
} from "../task/task-service.ts";
|
|
40
|
-
import type { TaskViewMode } from "../task-scope/task-scope.ts";
|
|
41
41
|
import {
|
|
42
42
|
booleanProp,
|
|
43
43
|
classifySessionAuthorization,
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,9 @@ export type { PlaybookInvocationResult, PlaybookMissingArguments } from "./playb
|
|
|
28
28
|
export type { WorkflowRunResult } from "./playbook/workflow-execution.ts";
|
|
29
29
|
export type { OperationName, SchemaState } from "./service.ts";
|
|
30
30
|
export { checklistEntries, PROOF_TYPES, type ProofReference } from "./task/checklist.ts";
|
|
31
|
+
export type { TaskEvent, TaskHistoryPage } from "./task/event/task-event.ts";
|
|
31
32
|
export type { TaskLease, TaskLeaseView } from "./task/lease/task-lease.ts";
|
|
33
|
+
export type { TaskViewSelection } from "./task/scope/task-scope.ts";
|
|
32
34
|
export { taskContext } from "./task/task-context.ts";
|
|
33
35
|
export { projectTaskExecution, type TaskExecutionPlan, type TaskExecutionState } from "./task/task-execution.ts";
|
|
34
36
|
export { projectTaskGraph, type TaskGraphView } from "./task/task-graph-view.ts";
|
|
@@ -42,5 +44,3 @@ export type {
|
|
|
42
44
|
TaskStatus,
|
|
43
45
|
} from "./task/task-service.ts";
|
|
44
46
|
export { TaskInvalidTransitionError, TaskMutationReceiptNotFoundError } from "./task/task-service.ts";
|
|
45
|
-
export type { TaskEvent, TaskHistoryPage } from "./task-event/task-event.ts";
|
|
46
|
-
export type { TaskViewSelection } from "./task-scope/task-scope.ts";
|
package/src/modules/playbooks.ts
CHANGED
|
@@ -40,9 +40,9 @@ import {
|
|
|
40
40
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
41
41
|
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
42
42
|
import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
|
|
43
|
+
import type { TaskEventSink } from "../task/event/task-event-store.ts";
|
|
44
|
+
import type { TaskScopeAssigner } from "../task/scope/task-scope-store.ts";
|
|
43
45
|
import type { Tasks } from "../task/task-service.ts";
|
|
44
|
-
import type { TaskEventStore } from "../task-event/task-event-store.ts";
|
|
45
|
-
import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
|
|
46
46
|
import { type OperationInput, optionalBoolean, optionalNumber, optionalString, string } from "./operation-input.ts";
|
|
47
47
|
|
|
48
48
|
const MODULE_ID = "playbooks";
|
|
@@ -105,8 +105,8 @@ export const PLAYBOOKS_OPERATION_NAMES = [
|
|
|
105
105
|
|
|
106
106
|
export interface PlaybooksModuleDeps {
|
|
107
107
|
artifacts: ArtifactStore;
|
|
108
|
-
events:
|
|
109
|
-
scopes:
|
|
108
|
+
events: TaskEventSink;
|
|
109
|
+
scopes: TaskScopeAssigner;
|
|
110
110
|
/** Docs/Rules/Skills/Playbooks project scoping (distinct from `scopes`, which is Task-run project scoping for playbooks.invoke's materialized tasks). */
|
|
111
111
|
artifactScopes: ArtifactScopeStore;
|
|
112
112
|
/** Used for exactly one thing: focusing the entry task after a successful invoke -- the one safety-checked Tasks operation this module needs, not bulk graph construction (that goes straight through artifacts/events/scopes in playbook/playbook-execution.ts, mirroring playbook/workflow-execution.ts). */
|
package/src/modules/projects.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { TASK_PROJECT_LIST_MAX_RESULTS } from "../constants.ts";
|
|
|
11
11
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
12
12
|
import { assertRegisterProjectInputBounds, resolveProjectReference } from "../project-registry/project-registry.ts";
|
|
13
13
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
14
|
-
import { normalizeProjectRoot } from "../
|
|
14
|
+
import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
|
|
15
15
|
import { type OperationInput, optionalNumber, optionalString, optionalStringArray, string } from "./operation-input.ts";
|
|
16
16
|
|
|
17
17
|
const MODULE_ID = "projects";
|
package/src/modules/tasks.ts
CHANGED
|
@@ -24,11 +24,11 @@ import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
|
24
24
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
25
25
|
import type { SessionIdentity } from "../session-identity/session-identity-service.ts";
|
|
26
26
|
import type { Checklist } from "../task/checklist.ts";
|
|
27
|
+
import type { TaskEventContext, TaskEventDirection, TaskEventFeedQuery } from "../task/event/task-event.ts";
|
|
28
|
+
import type { TaskViewMode } from "../task/scope/task-scope.ts";
|
|
27
29
|
import { taskContext } from "../task/task-context.ts";
|
|
28
30
|
import { projectTaskExecution } from "../task/task-execution.ts";
|
|
29
31
|
import type { TaskMutationRequestContext, TaskStatus, Tasks } from "../task/task-service.ts";
|
|
30
|
-
import type { TaskEventContext, TaskEventDirection, TaskEventFeedQuery } from "../task-event/task-event.ts";
|
|
31
|
-
import type { TaskViewMode } from "../task-scope/task-scope.ts";
|
|
32
32
|
import { type OperationInput, optionalBoolean, optionalNumber, optionalString, optionalStringArray, string } from "./operation-input.ts";
|
|
33
33
|
|
|
34
34
|
const MODULE_ID = "tasks";
|
|
@@ -57,8 +57,8 @@ import {
|
|
|
57
57
|
type UpdateContentInput,
|
|
58
58
|
} from "../domain-service-shared.ts";
|
|
59
59
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
60
|
+
import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
|
|
60
61
|
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
61
|
-
import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
|
|
62
62
|
import {
|
|
63
63
|
BLUEPRINT_INPUT_TYPES,
|
|
64
64
|
type BlueprintArgumentValue,
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
SKILL_WORKFLOW_MAX_NESTING_DEPTH,
|
|
10
10
|
TASK_EXECUTION_MAX_EDGES,
|
|
11
11
|
} from "../constants.ts";
|
|
12
|
+
import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
|
|
12
13
|
import { validateChecklist } from "../task/checklist.ts";
|
|
14
|
+
import type { TaskEventContext } from "../task/event/task-event.ts";
|
|
15
|
+
import type { TaskEventSink } from "../task/event/task-event-store.ts";
|
|
16
|
+
import type { TaskScopeAssigner } from "../task/scope/task-scope-store.ts";
|
|
13
17
|
import { projectTaskExecution, TaskExecutionBoundExceededError, type TaskExecutionPlan } from "../task/task-execution.ts";
|
|
14
18
|
import type { TaskGraph, TaskNode, TaskStatus } from "../task/task-service.ts";
|
|
15
|
-
import type { TaskEventContext } from "../task-event/task-event.ts";
|
|
16
|
-
import type { TaskEventStore } from "../task-event/task-event-store.ts";
|
|
17
|
-
import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
|
|
18
|
-
import type { TaskScopeStore } from "../task-scope/task-scope-store.ts";
|
|
19
19
|
import {
|
|
20
20
|
type BlueprintArgumentValue,
|
|
21
21
|
type BlueprintDefinition,
|
|
@@ -167,8 +167,8 @@ function executionGraph(tasks: Artifact[], definition: BlueprintDefinition, ids:
|
|
|
167
167
|
* other project's context.
|
|
168
168
|
*/
|
|
169
169
|
export type WorkflowRunHistory = {
|
|
170
|
-
events:
|
|
171
|
-
scopes:
|
|
170
|
+
events: TaskEventSink;
|
|
171
|
+
scopes: TaskScopeAssigner;
|
|
172
172
|
artifactScopes?: ArtifactScopeStore;
|
|
173
173
|
projectRoot?: string;
|
|
174
174
|
context?: TaskEventContext;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { basename, isAbsolute, normalize } from "node:path";
|
|
2
|
+
import { TASK_PROJECT_ROOT_MAX_LENGTH } from "../constants.ts";
|
|
3
|
+
|
|
4
|
+
/** How a project root got attached to a scoped artifact -- shared across Tasks/Docs/Rules/Playbooks, same category as Project in project-registry.ts. */
|
|
5
|
+
export type ScopeAssignmentSource = "cwd" | "explicit" | "unscoped";
|
|
6
|
+
|
|
7
|
+
export function normalizeProjectRoot(value: string): string {
|
|
8
|
+
if (!isAbsolute(value)) throw new Error("project_root must be an absolute path");
|
|
9
|
+
const normalized = normalize(value);
|
|
10
|
+
if (normalized.length > TASK_PROJECT_ROOT_MAX_LENGTH) {
|
|
11
|
+
throw new Error(`project_root cannot exceed ${TASK_PROJECT_ROOT_MAX_LENGTH} characters`);
|
|
12
|
+
}
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
type UpdateContentInput,
|
|
32
32
|
} from "../domain-service-shared.ts";
|
|
33
33
|
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
34
|
+
import { normalizeProjectRoot } from "../project-registry/scope-source.ts";
|
|
34
35
|
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
35
|
-
import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
|
|
36
36
|
|
|
37
37
|
export interface CreateRuleInput {
|
|
38
38
|
title: string;
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { SCOPE_GROUP_ALIAS_MAX_COUNT, SCOPE_GROUP_NAME_MAX_LENGTH } from "../constants.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A named, reusable collection of scope members (registered projects and/or other scope groups)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Deliberately explicit/opt-in membership, not filesystem-path-derived nesting: a registered
|
|
10
|
-
* project whose root happens to be a subdirectory of another registered project's root is NOT
|
|
11
|
-
* automatically its child (a real false-positive confirmed live: alignment-lector is registered
|
|
12
|
-
* under lector/packages/alignment-lector, a subdirectory of lector's own root, but is Lector's
|
|
13
|
-
* adapter for a *different* project, not part of Lector's own scope). A group's membership is
|
|
14
|
-
* only ever what was deliberately added to it.
|
|
4
|
+
* A named, reusable collection of scope members (registered projects and/or other scope groups).
|
|
5
|
+
* Membership is explicit/opt-in, never path-derived: a project registered under another
|
|
6
|
+
* project's root directory (e.g. a vendored adapter package) is not automatically that parent's
|
|
7
|
+
* child -- only what's deliberately added counts.
|
|
15
8
|
*/
|
|
16
9
|
export interface ScopeGroup {
|
|
17
10
|
id: string;
|
package/src/service.ts
CHANGED
|
@@ -44,16 +44,16 @@ import { SQLiteScopeGroupStore } from "./scope-group/sqlite-scope-group-store.ts
|
|
|
44
44
|
import { InvalidSessionSecretError, SessionIdentity } from "./session-identity/session-identity-service.ts";
|
|
45
45
|
import { SQLiteSessionIdentityStore } from "./session-identity/sqlite-session-identity-store.ts";
|
|
46
46
|
import { SQLiteTaskCreateRequestStore } from "./task/create-request/sqlite-task-create-request-store.ts";
|
|
47
|
+
import { SQLiteTaskEventStore } from "./task/event/sqlite-task-event-store.ts";
|
|
48
|
+
import type { TaskEventContext } from "./task/event/task-event.ts";
|
|
49
|
+
import type { TaskEventStore } from "./task/event/task-event-store.ts";
|
|
47
50
|
import { SQLiteTaskFocusStore } from "./task/focus/sqlite-task-focus-store.ts";
|
|
48
51
|
import { SQLiteTaskLeaseStore } from "./task/lease/sqlite-task-lease-store.ts";
|
|
49
52
|
import { SQLiteTaskMutationRequestStore } from "./task/mutation-request/sqlite-task-mutation-request-store.ts";
|
|
53
|
+
import { SQLiteTaskScopeStore } from "./task/scope/sqlite-task-scope-store.ts";
|
|
54
|
+
import type { TaskViewMode } from "./task/scope/task-scope.ts";
|
|
55
|
+
import type { TaskScopeStore } from "./task/scope/task-scope-store.ts";
|
|
50
56
|
import { type TaskStatus, Tasks } from "./task/task-service.ts";
|
|
51
|
-
import { SQLiteTaskEventStore } from "./task-event/sqlite-task-event-store.ts";
|
|
52
|
-
import type { TaskEventContext } from "./task-event/task-event.ts";
|
|
53
|
-
import type { TaskEventStore } from "./task-event/task-event-store.ts";
|
|
54
|
-
import { SQLiteTaskScopeStore } from "./task-scope/sqlite-task-scope-store.ts";
|
|
55
|
-
import type { TaskViewMode } from "./task-scope/task-scope.ts";
|
|
56
|
-
import type { TaskScopeStore } from "./task-scope/task-scope-store.ts";
|
|
57
57
|
import { VERSION } from "./version.ts";
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Db } from "
|
|
2
|
-
import { inTransaction } from "
|
|
1
|
+
import type { Db } from "../../db.ts";
|
|
2
|
+
import { inTransaction } from "../../db.ts";
|
|
3
3
|
import {
|
|
4
4
|
type AppendTaskEvent,
|
|
5
5
|
normalizeTaskEventFeedQuery,
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
type TaskHistoryQuery,
|
|
14
14
|
type TaskLifecycleStatus,
|
|
15
15
|
validateTaskEvent,
|
|
16
|
-
} from "
|
|
17
|
-
import type { TaskEventStore } from "
|
|
16
|
+
} from "./task-event.ts";
|
|
17
|
+
import type { TaskEventStore } from "./task-event-store.ts";
|
|
18
18
|
|
|
19
19
|
interface TaskEventRow {
|
|
20
20
|
id: number;
|
|
@@ -8,11 +8,15 @@ import {
|
|
|
8
8
|
type TaskHistoryPage,
|
|
9
9
|
type TaskHistoryQuery,
|
|
10
10
|
validateTaskEvent,
|
|
11
|
-
} from "
|
|
11
|
+
} from "./task-event.ts";
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/** What a caller outside Task needs to append events -- history/feed stay on TaskEventStore since only Task's own readers use them. */
|
|
14
|
+
export interface TaskEventSink {
|
|
14
15
|
atomic<T>(operation: () => T): T;
|
|
15
16
|
append(event: AppendTaskEvent): TaskEvent;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TaskEventStore extends TaskEventSink {
|
|
16
20
|
history(taskId: string, query?: TaskHistoryQuery): TaskHistoryPage;
|
|
17
21
|
/** Bounded, sequenced, cross-task replay feed -- see TaskEventFeedQuery. */
|
|
18
22
|
feed(query?: TaskEventFeedQuery): TaskEventFeedPage;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
TASK_EVENT_REASON_MAX_LENGTH,
|
|
7
7
|
TASK_HISTORY_DEFAULT_LIMIT,
|
|
8
8
|
TASK_HISTORY_MAX_LIMIT,
|
|
9
|
-
} from "
|
|
9
|
+
} from "../../constants.ts";
|
|
10
10
|
export type TaskLifecycleStatus = "todo" | "in-progress" | "review" | "rejected" | "done" | "canceled";
|
|
11
11
|
|
|
12
12
|
export const TASK_EVENT_TYPES = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Db } from "
|
|
2
|
-
import { inTransaction } from "
|
|
3
|
-
import { SQLiteProjectRegistryStore } from "
|
|
1
|
+
import type { Db } from "../../db.ts";
|
|
2
|
+
import { inTransaction } from "../../db.ts";
|
|
3
|
+
import { SQLiteProjectRegistryStore } from "../../project-registry/sqlite-project-registry-store.ts";
|
|
4
4
|
import type {
|
|
5
5
|
RegisterTaskProjectInput,
|
|
6
6
|
TaskProject,
|
|
@@ -8,8 +8,8 @@ import type {
|
|
|
8
8
|
TaskScopeSource,
|
|
9
9
|
TaskViewMode,
|
|
10
10
|
TaskViewPreference,
|
|
11
|
-
} from "
|
|
12
|
-
import type { TaskScopeStore } from "
|
|
11
|
+
} from "./task-scope.ts";
|
|
12
|
+
import type { TaskScopeStore } from "./task-scope-store.ts";
|
|
13
13
|
|
|
14
14
|
/** Rewrites every task_scopes/task_views row pinned to a project root that just moved -- called inside the same registerProject() transaction, so a rename/move is atomic with the rewrite. */
|
|
15
15
|
function rewriteTaskRowsForMovedRoot(db: Db, previousRoot: string, nextRoot: string): void {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { InMemoryProjectRegistryStore } from "
|
|
2
|
-
import type { ProjectRegistryStore } from "
|
|
1
|
+
import { InMemoryProjectRegistryStore } from "../../project-registry/in-memory-project-registry-store.ts";
|
|
2
|
+
import type { ProjectRegistryStore } from "../../project-registry/project-registry-store.ts";
|
|
3
3
|
import type {
|
|
4
4
|
RegisterTaskProjectInput,
|
|
5
5
|
TaskProject,
|
|
@@ -7,10 +7,14 @@ import type {
|
|
|
7
7
|
TaskScopeSource,
|
|
8
8
|
TaskViewMode,
|
|
9
9
|
TaskViewPreference,
|
|
10
|
-
} from "
|
|
10
|
+
} from "./task-scope.ts";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/** What a caller outside Task needs to assign a task's project scope -- the rest of TaskScopeStore (catalog, views, listing) stays Task-only. */
|
|
13
|
+
export interface TaskScopeAssigner {
|
|
13
14
|
assign(taskId: string, projectRoot: string | undefined, source: TaskScopeSource): TaskProjectScope;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TaskScopeStore extends TaskScopeAssigner {
|
|
14
18
|
get(taskId: string): TaskProjectScope | undefined;
|
|
15
19
|
taskIds(projectRoot: string | undefined, limit: number): string[];
|
|
16
20
|
view(projectRoot: string): TaskViewPreference;
|
|
@@ -20,13 +24,7 @@ export interface TaskScopeStore {
|
|
|
20
24
|
registerProject(input: RegisterTaskProjectInput): TaskProject;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
/**
|
|
24
|
-
* Task's own scope/view bookkeeping, composing a ProjectRegistryStore for the project-catalog
|
|
25
|
-
* methods (projects/matchingProjects/registerProject) rather than implementing that bookkeeping
|
|
26
|
-
* itself -- see project-registry-store.ts. Pass a shared registry instance to keep Task and a
|
|
27
|
-
* non-Task ArtifactScopeStore resolving against the exact same project identities; omitted, this
|
|
28
|
-
* store gets its own private one (matching this class's own behavior before the extraction).
|
|
29
|
-
*/
|
|
27
|
+
/** Pass a shared registry to keep Task and ArtifactScopeStore resolving the same project identities; omitted, this gets its own private one. */
|
|
30
28
|
export class InMemoryTaskScopeStore implements TaskScopeStore {
|
|
31
29
|
private readonly scopes = new Map<string, TaskProjectScope>();
|
|
32
30
|
private readonly views = new Map<string, TaskViewPreference>();
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { basename
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import type { Project, RegisterProjectInput } from "../../project-registry/project-registry.ts";
|
|
3
|
+
import type { ScopeAssignmentSource } from "../../project-registry/scope-source.ts";
|
|
4
|
+
|
|
5
|
+
export { normalizeProjectRoot } from "../../project-registry/scope-source.ts";
|
|
4
6
|
|
|
5
7
|
export type TaskViewMode = "project" | "graph" | "all";
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
/** Alias of ScopeAssignmentSource for Task's own call sites -- see project-registry/scope-source.ts. */
|
|
10
|
+
export type TaskScopeSource = ScopeAssignmentSource;
|
|
7
11
|
|
|
8
12
|
export interface TaskProjectScope {
|
|
9
13
|
taskId: string;
|
|
@@ -29,15 +33,6 @@ export interface TaskViewSelection {
|
|
|
29
33
|
rootTaskId?: string;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
export function normalizeProjectRoot(value: string): string {
|
|
33
|
-
if (!isAbsolute(value)) throw new Error("project_root must be an absolute path");
|
|
34
|
-
const normalized = normalize(value);
|
|
35
|
-
if (normalized.length > TASK_PROJECT_ROOT_MAX_LENGTH) {
|
|
36
|
-
throw new Error(`project_root cannot exceed ${TASK_PROJECT_ROOT_MAX_LENGTH} characters`);
|
|
37
|
-
}
|
|
38
|
-
return normalized;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
36
|
export function taskScopeLabel(mode: TaskViewMode, projectRoot?: string, rootTitle?: string): string {
|
|
42
37
|
if (mode === "all") return "All projects";
|
|
43
38
|
const project = projectRoot ? basename(projectRoot) || projectRoot : "Unscoped";
|
package/src/task/task-edges.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Artifact, ArtifactEdge } from "../artifact/artifact.ts";
|
|
2
2
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
3
3
|
import { TASK_EXECUTION_MAX_DEGREE } from "../constants.ts";
|
|
4
|
-
import type { AppendTaskEvent, TaskEventContext } from "
|
|
5
|
-
import type { TaskEventStore } from "
|
|
4
|
+
import type { AppendTaskEvent, TaskEventContext } from "./event/task-event.ts";
|
|
5
|
+
import type { TaskEventStore } from "./event/task-event-store.ts";
|
|
6
6
|
import { assertDependencyEdgeAllowed, TaskExecutionBoundExceededError } from "./task-execution.ts";
|
|
7
7
|
// Type-only import: erased entirely at compile time, so this does not create a real runtime
|
|
8
8
|
// circular dependency even though task-service.ts also imports TaskEdges (a real value) from
|
|
@@ -10,17 +10,10 @@ import { assertDependencyEdgeAllowed, TaskExecutionBoundExceededError } from "./
|
|
|
10
10
|
import type { TaskGraph } from "./task-service.ts";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Task dependency/containment edge mutations (depend/undepend/contain/uncontain)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* TaskProjectScope precedent in this same directory.
|
|
18
|
-
*
|
|
19
|
-
* Unlike those simpler extractions, this one only owns the mutation side of edges -- it reads the
|
|
20
|
-
* graph/relationships it needs through injected callbacks (dependencyCheckGraph/dependencyIds/
|
|
21
|
-
* relationships) rather than duplicating that graph-construction machinery, since those reads are
|
|
22
|
-
* shared with concerns that stay on Tasks (list/graph/buildGraph, progress propagation, blockage
|
|
23
|
-
* checks in transition/complete).
|
|
13
|
+
* Task dependency/containment edge mutations (depend/undepend/contain/uncontain). Reads the
|
|
14
|
+
* graph/relationships it needs through injected callbacks rather than owning graph construction,
|
|
15
|
+
* since those reads are shared with concerns that stay on Tasks (list/graph, progress propagation,
|
|
16
|
+
* blockage checks).
|
|
24
17
|
*/
|
|
25
18
|
export class TaskEdges {
|
|
26
19
|
constructor(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Artifact } from "../artifact/artifact.ts";
|
|
2
2
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
3
3
|
import { TASK_FOCUS_STALE_AFTER_MS } from "../constants.ts";
|
|
4
|
-
import { type AppendTaskEvent, type TaskEventContext, validateEventContext } from "
|
|
5
|
-
import type { TaskEventStore } from "
|
|
4
|
+
import { type AppendTaskEvent, type TaskEventContext, validateEventContext } from "./event/task-event.ts";
|
|
5
|
+
import type { TaskEventStore } from "./event/task-event-store.ts";
|
|
6
6
|
import type { TaskFocusStatus, TaskFocusStore } from "./focus/task-focus-store.ts";
|
|
7
7
|
import { TaskInvalidTransitionError } from "./task-lifecycle-errors.ts";
|
|
8
8
|
import type { TaskMutationCoordinator, TaskMutationRequestContext } from "./task-mutation-coordinator.ts";
|
|
@@ -21,14 +21,8 @@ export interface TaskFocus {
|
|
|
21
21
|
export type TaskFocusMutationResult = TaskFocus & TaskMutationMetadata;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Task Focus (the single active/paused task per session scope)
|
|
25
|
-
*
|
|
26
|
-
* child of "Epic: Modularize papyrus/pi-papyrus god-files into building-block modules"), mirroring
|
|
27
|
-
* the existing TaskLeaseCoordinator/TaskMutationCoordinator precedent in this same directory.
|
|
28
|
-
*
|
|
29
|
-
* Focus is orthogonal to lifecycle and lease: focusing a task does not start it, and does not
|
|
30
|
-
* claim its lease -- so this concern has nothing to do with status transitions or worker
|
|
31
|
-
* exclusivity, the other concerns that were previously interleaved with it in one class.
|
|
24
|
+
* Task Focus (the single active/paused task per session scope). Orthogonal to lifecycle and
|
|
25
|
+
* lease: focusing a task does not start it and does not claim its lease.
|
|
32
26
|
*/
|
|
33
27
|
export class TaskFocusCoordinator {
|
|
34
28
|
constructor(
|
|
@@ -3,12 +3,9 @@ import type { TaskLease, TaskLeaseView } from "./lease/task-lease.ts";
|
|
|
3
3
|
import type { TaskLeaseStore } from "./lease/task-lease-store.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Task lease management (claim/heartbeat/release/get/reap)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Focused -- so its own concern (a single active worker per task, TTL-based) has nothing to do
|
|
10
|
-
* with status transitions, idempotency receipts, or checklist review, the other concerns that
|
|
11
|
-
* were previously interleaved with it in one class.
|
|
6
|
+
* Task lease management (claim/heartbeat/release/get/reap) -- a single active worker per task,
|
|
7
|
+
* TTL-based. Orthogonal to lifecycle and Focus: claiming a task does not start it or require it
|
|
8
|
+
* to be Focused.
|
|
12
9
|
*/
|
|
13
10
|
export class TaskLeaseCoordinator {
|
|
14
11
|
constructor(
|
|
@@ -41,26 +41,16 @@ function canonicalJson(value: unknown): string {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Idempotency-key-backed mutation receipt plumbing
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* different payload reused under the same key, and rejecting a NEW attempt against a
|
|
48
|
-
* task+operation that already has one in flight.
|
|
44
|
+
* Idempotency-key-backed mutation receipt plumbing: reserves a "pending" receipt before a real
|
|
45
|
+
* mutation runs, replays an already-completed one, rejects a different payload reused under the
|
|
46
|
+
* same key, and rejects a new attempt against a task+operation that already has one in flight.
|
|
49
47
|
*
|
|
50
|
-
* `validate`, when
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* the receipt as pending. Once that validation threw, nothing downstream ever reached the code
|
|
57
|
-
* path that marks a receipt complete, permanently stranding it -- and since the pending-mutation
|
|
58
|
-
* lock is keyed on (taskId, operation) rather than the idempotency key, that stuck receipt then
|
|
59
|
-
* blocked every subsequent attempt on the same task+operation, under ANY key, until the record's
|
|
60
|
-
* 7-day retention window expired. No self-service recovery existed; the live incident required a
|
|
61
|
-
* direct database row deletion. Every caller that can determine its own event-context validity up
|
|
62
|
-
* front (reason/sessionId length, at minimum) should now pass a `validate` callback here instead
|
|
63
|
-
* of only validating once real mutation work is already underway.
|
|
48
|
+
* `validate`, when passed to prepare(), runs before the existing/replay lookup, so a
|
|
49
|
+
* caller-supplied validation failure can never leave a receipt reserved with no way to complete
|
|
50
|
+
* it -- a stuck pending receipt blocks every later attempt on that task+operation, under any key,
|
|
51
|
+
* until its retention window expires, with no self-service recovery. Any caller that can
|
|
52
|
+
* determine its own event-context validity up front should pass `validate` here rather than
|
|
53
|
+
* validating only once real mutation work is underway.
|
|
64
54
|
*/
|
|
65
55
|
export class TaskMutationCoordinator {
|
|
66
56
|
constructor(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Artifact } from "../artifact/artifact.ts";
|
|
2
2
|
import { TASK_PROJECT_LIST_MAX_RESULTS } from "../constants.ts";
|
|
3
3
|
import { assertRegisterProjectInputBounds } from "../project-registry/project-registry.ts";
|
|
4
|
-
import type { AppendTaskEvent, TaskEventContext } from "
|
|
5
|
-
import type { TaskEventStore } from "
|
|
4
|
+
import type { AppendTaskEvent, TaskEventContext } from "./event/task-event.ts";
|
|
5
|
+
import type { TaskEventStore } from "./event/task-event-store.ts";
|
|
6
6
|
import {
|
|
7
7
|
normalizeProjectRoot,
|
|
8
8
|
type RegisterTaskProjectInput,
|
|
@@ -10,21 +10,16 @@ import {
|
|
|
10
10
|
type TaskViewMode,
|
|
11
11
|
type TaskViewSelection,
|
|
12
12
|
taskScopeLabel,
|
|
13
|
-
} from "
|
|
14
|
-
import type { TaskScopeStore } from "
|
|
13
|
+
} from "./scope/task-scope.ts";
|
|
14
|
+
import type { TaskScopeStore } from "./scope/task-scope-store.ts";
|
|
15
15
|
|
|
16
16
|
export class TaskProjectNotFoundError extends Error {}
|
|
17
17
|
export class TaskProjectAmbiguousError extends Error {}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Task project-scope management (scopeSelection/setView/assignProject/projects/resolveProject/
|
|
21
|
-
* registerProject)
|
|
22
|
-
*
|
|
23
|
-
* papyrus/pi-papyrus god-files into building-block modules"), mirroring the existing
|
|
24
|
-
* TaskLeaseCoordinator/TaskMutationCoordinator/TaskFocusCoordinator precedent in this directory.
|
|
25
|
-
*
|
|
26
|
-
* list()/graph() (which stay on Tasks -- they're core query/graph-construction, not project-scope
|
|
27
|
-
* itself) still call scopeSelection() on this collaborator via Tasks' own thin delegation.
|
|
21
|
+
* registerProject). list()/graph() stay on Tasks but still call scopeSelection() here via a thin
|
|
22
|
+
* delegation, since those are core query/graph-construction, not project-scope itself.
|
|
28
23
|
*/
|
|
29
24
|
export class TaskProjectScope {
|
|
30
25
|
constructor(
|
package/src/task/task-service.ts
CHANGED
|
@@ -18,6 +18,12 @@ import { DISCUSSION_SUBTYPE, isDiscussionArtifact, readDiscussionExtra } from ".
|
|
|
18
18
|
import type { TransitionTable } from "../domain-service-shared.ts";
|
|
19
19
|
import { type Gate, type GateResult, validateGates } from "../gate/gate.ts";
|
|
20
20
|
import type { GateRunner } from "../gate/gate-runner.ts";
|
|
21
|
+
import { type Checklist, checklistEntries, type ProofReference, validateChecklist } from "./checklist.ts";
|
|
22
|
+
import {
|
|
23
|
+
InMemoryTaskCreateRequestStore,
|
|
24
|
+
TaskCreateIdempotencyConflictError,
|
|
25
|
+
type TaskCreateRequestStore,
|
|
26
|
+
} from "./create-request/task-create-request-store.ts";
|
|
21
27
|
import type {
|
|
22
28
|
AppendTaskEvent,
|
|
23
29
|
TaskEventContext,
|
|
@@ -26,25 +32,9 @@ import type {
|
|
|
26
32
|
TaskHistoryPage,
|
|
27
33
|
TaskHistoryQuery,
|
|
28
34
|
TaskLifecycleStatus,
|
|
29
|
-
} from "
|
|
30
|
-
import { validateEventContext } from "
|
|
31
|
-
import { InMemoryTaskEventStore, type TaskEventStore } from "
|
|
32
|
-
import {
|
|
33
|
-
normalizeProjectRoot,
|
|
34
|
-
type RegisterTaskProjectInput,
|
|
35
|
-
type TaskProject,
|
|
36
|
-
type TaskScopeSource,
|
|
37
|
-
type TaskViewMode,
|
|
38
|
-
type TaskViewSelection,
|
|
39
|
-
taskScopeLabel,
|
|
40
|
-
} from "../task-scope/task-scope.ts";
|
|
41
|
-
import { InMemoryTaskScopeStore, type TaskScopeStore } from "../task-scope/task-scope-store.ts";
|
|
42
|
-
import { type Checklist, checklistEntries, type ProofReference, validateChecklist } from "./checklist.ts";
|
|
43
|
-
import {
|
|
44
|
-
InMemoryTaskCreateRequestStore,
|
|
45
|
-
TaskCreateIdempotencyConflictError,
|
|
46
|
-
type TaskCreateRequestStore,
|
|
47
|
-
} from "./create-request/task-create-request-store.ts";
|
|
35
|
+
} from "./event/task-event.ts";
|
|
36
|
+
import { validateEventContext } from "./event/task-event.ts";
|
|
37
|
+
import { InMemoryTaskEventStore, type TaskEventStore } from "./event/task-event-store.ts";
|
|
48
38
|
import { InMemoryTaskFocusStore, type TaskFocusStatus, type TaskFocusStore } from "./focus/task-focus-store.ts";
|
|
49
39
|
import type { TaskLeaseView } from "./lease/task-lease.ts";
|
|
50
40
|
import { InMemoryTaskLeaseStore, type TaskLeaseStore } from "./lease/task-lease-store.ts";
|
|
@@ -54,6 +44,16 @@ import {
|
|
|
54
44
|
type TaskMutationRequestRecord,
|
|
55
45
|
type TaskMutationRequestStore,
|
|
56
46
|
} from "./mutation-request/task-mutation-request-store.ts";
|
|
47
|
+
import {
|
|
48
|
+
normalizeProjectRoot,
|
|
49
|
+
type RegisterTaskProjectInput,
|
|
50
|
+
type TaskProject,
|
|
51
|
+
type TaskScopeSource,
|
|
52
|
+
type TaskViewMode,
|
|
53
|
+
type TaskViewSelection,
|
|
54
|
+
taskScopeLabel,
|
|
55
|
+
} from "./scope/task-scope.ts";
|
|
56
|
+
import { InMemoryTaskScopeStore, type TaskScopeStore } from "./scope/task-scope-store.ts";
|
|
57
57
|
import { TaskEdges } from "./task-edges.ts";
|
|
58
58
|
import { TaskExecutionBoundExceededError } from "./task-execution.ts";
|
|
59
59
|
import { type TaskFocus, TaskFocusCoordinator, type TaskFocusMutationResult } from "./task-focus-coordinator.ts";
|