@danypops/papyrus 0.56.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/artifact/artifact-relationship-view.ts +1 -1
- package/src/artifact/artifact-scope-store.ts +32 -26
- package/src/artifact/in-memory-artifact-scope-store.ts +82 -43
- package/src/artifact/sqlite-artifact-scope-store.ts +105 -56
- package/src/cli/docs-command.ts +71 -0
- package/src/cli/gates-command.ts +1 -1
- package/src/cli/note-command.ts +1 -1
- package/src/cli/playbooks-command.ts +71 -0
- package/src/cli/rules-command.ts +71 -0
- package/src/cli/scope-groups-command.ts +197 -0
- package/src/cli/task-command.ts +1 -1
- package/src/cli.ts +7 -0
- package/src/constants.ts +10 -2
- package/src/db.ts +117 -7
- package/src/{stores → discussion}/discussion-round-store.ts +1 -1
- package/src/discussion/discussion-service.ts +2 -2
- package/src/{stores → discussion}/sqlite-discussion-round-store.ts +2 -2
- package/src/display-graph/graph-renderer.ts +5 -0
- package/src/docs/docs-service.ts +55 -20
- package/src/domain-service-shared.ts +90 -2
- package/src/{domain → gate}/gate-execution.ts +1 -1
- package/src/{stores → gate}/gate-runner.ts +1 -1
- package/src/{stores → gate}/sqlite-gate-runner.ts +3 -3
- package/src/graph-projection/graph-projection-service.ts +2 -2
- package/src/{stores → graph-projection}/graph-projection-store.ts +1 -1
- package/src/{stores → graph-projection}/sqlite-graph-projection-store.ts +2 -2
- package/src/handlers/discuss.ts +1 -1
- package/src/handlers/docs.ts +57 -2
- package/src/handlers/playbooks.ts +49 -8
- package/src/handlers/projects.ts +1 -1
- package/src/handlers/registry.ts +10 -5
- package/src/handlers/rules.ts +66 -2
- package/src/handlers/scope-groups.ts +100 -0
- package/src/handlers/task-classifiers.ts +1 -1
- package/src/handlers/tasks.ts +4 -4
- package/src/index.ts +9 -9
- package/src/log/log-service.ts +2 -2
- package/src/{stores → log}/log-store.ts +1 -1
- package/src/{stores → log}/sqlite-log-store.ts +2 -2
- package/src/modules/docs.ts +21 -1
- package/src/modules/graph-projection.ts +2 -2
- package/src/modules/logs.ts +1 -1
- package/src/modules/notes.ts +1 -1
- package/src/modules/playbooks.ts +24 -3
- package/src/modules/projects.ts +3 -3
- package/src/modules/rules.ts +21 -1
- package/src/modules/scope-groups.ts +92 -0
- package/src/modules/tasks.ts +3 -3
- package/src/{stores → note}/note-event-store.ts +1 -1
- package/src/note/note-service.ts +2 -2
- package/src/{stores → note}/sqlite-note-event-store.ts +2 -2
- package/src/ops.ts +1 -1
- package/src/playbook/playbook-definition.ts +2 -2
- package/src/playbook/playbook-execution.ts +1 -1
- package/src/playbook/playbook-service.ts +61 -26
- package/src/playbook/workflow-execution.ts +8 -8
- package/src/{stores → project-registry}/in-memory-project-registry-store.ts +2 -2
- package/src/{ports → project-registry}/project-registry-store.ts +1 -1
- package/src/{stores → project-registry}/sqlite-project-registry-store.ts +3 -3
- package/src/rules/rules-service.ts +56 -24
- package/src/scope-group/delete-scope-group.ts +17 -0
- package/src/scope-group/in-memory-scope-group-store.ts +173 -0
- package/src/scope-group/scope-group-store.ts +28 -0
- package/src/scope-group/scope-group.ts +79 -0
- package/src/scope-group/sqlite-scope-group-store.ts +223 -0
- package/src/service.ts +46 -21
- package/src/session-identity/session-identity-service.ts +2 -2
- package/src/{stores → session-identity}/sqlite-session-identity-store.ts +1 -1
- package/src/task/task-context.ts +1 -1
- package/src/task/task-edges.ts +2 -2
- package/src/task/task-focus-coordinator.ts +3 -3
- package/src/task/task-graph-view.ts +1 -1
- package/src/task/task-lease-coordinator.ts +2 -2
- package/src/task/task-mutation-coordinator.ts +1 -1
- package/src/task/task-project-scope.ts +5 -5
- package/src/task/task-relationship-view.ts +1 -1
- package/src/task/task-service.ts +24 -24
- package/src/{stores → task-create-request}/sqlite-task-create-request-store.ts +1 -1
- package/src/{stores → task-event}/sqlite-task-event-store.ts +2 -2
- package/src/{stores → task-event}/task-event-store.ts +1 -1
- package/src/{stores → task-focus}/sqlite-task-focus-store.ts +1 -1
- package/src/{stores → task-lease}/sqlite-task-lease-store.ts +2 -2
- package/src/{stores → task-lease}/task-lease-store.ts +1 -1
- package/src/{stores → task-mutation-request}/sqlite-task-mutation-request-store.ts +5 -1
- package/src/{stores → task-scope}/sqlite-task-scope-store.ts +3 -3
- package/src/{stores → task-scope}/task-scope-store.ts +3 -3
- package/src/{domain → task-scope}/task-scope.ts +1 -1
- package/src/domain/index.ts +0 -8
- package/src/stores/graph-renderer.ts +0 -5
- package/src/stores/index.ts +0 -1
- /package/src/{domain → discussion}/discussion.ts +0 -0
- /package/src/{domain → display-graph}/display-graph.ts +0 -0
- /package/src/{domain → gate}/gate.ts +0 -0
- /package/src/{domain → graph-projection}/graph-projection.ts +0 -0
- /package/src/{domain → log}/log-entry.ts +0 -0
- /package/src/{domain → note}/note-event.ts +0 -0
- /package/src/{domain → playbook}/blueprint-definition.ts +0 -0
- /package/src/{domain → project-registry}/project-registry.ts +0 -0
- /package/src/{stores → session-identity}/session-identity-store.ts +0 -0
- /package/src/{domain → session-identity}/session-identity.ts +0 -0
- /package/src/{domain → task}/checklist.ts +0 -0
- /package/src/{stores → task-create-request}/task-create-request-store.ts +0 -0
- /package/src/{domain → task-event}/task-event.ts +0 -0
- /package/src/{stores → task-focus}/task-focus-store.ts +0 -0
- /package/src/{domain → task-lease}/task-lease.ts +0 -0
- /package/src/{stores → task-mutation-request}/task-mutation-request-store.ts +0 -0
package/src/db.ts
CHANGED
|
@@ -233,17 +233,34 @@ CREATE INDEX IF NOT EXISTS graph_projection_identities_artifact_idx ON graph_pro
|
|
|
233
233
|
CREATE TABLE IF NOT EXISTS artifact_scopes (
|
|
234
234
|
artifact_id TEXT PRIMARY KEY REFERENCES artifacts(id),
|
|
235
235
|
project_root TEXT,
|
|
236
|
-
mode TEXT NOT NULL DEFAULT '
|
|
236
|
+
mode TEXT NOT NULL DEFAULT 'all' CHECK (mode IN ('none', 'all', 'explicit')),
|
|
237
237
|
source TEXT NOT NULL CHECK (source IN ('cwd', 'explicit', 'unscoped')),
|
|
238
238
|
assigned_at TEXT NOT NULL
|
|
239
239
|
);
|
|
240
240
|
CREATE INDEX IF NOT EXISTS artifact_scopes_project_idx ON artifact_scopes(project_root, artifact_id);
|
|
241
|
-
CREATE TABLE IF NOT EXISTS
|
|
241
|
+
CREATE TABLE IF NOT EXISTS artifact_scope_members (
|
|
242
242
|
artifact_id TEXT NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
member_type TEXT NOT NULL CHECK (member_type IN ('project', 'group')),
|
|
244
|
+
member_id TEXT NOT NULL,
|
|
245
|
+
PRIMARY KEY (artifact_id, member_type, member_id)
|
|
245
246
|
);
|
|
246
|
-
CREATE INDEX IF NOT EXISTS
|
|
247
|
+
CREATE INDEX IF NOT EXISTS artifact_scope_members_member_idx ON artifact_scope_members(member_type, member_id, artifact_id);
|
|
248
|
+
CREATE TABLE IF NOT EXISTS scope_groups (
|
|
249
|
+
id TEXT PRIMARY KEY,
|
|
250
|
+
name TEXT NOT NULL,
|
|
251
|
+
aliases_json TEXT NOT NULL DEFAULT '[]',
|
|
252
|
+
created_at TEXT NOT NULL,
|
|
253
|
+
updated_at TEXT NOT NULL
|
|
254
|
+
);
|
|
255
|
+
CREATE INDEX IF NOT EXISTS scope_groups_name_idx ON scope_groups(name);
|
|
256
|
+
CREATE TABLE IF NOT EXISTS scope_group_members (
|
|
257
|
+
group_id TEXT NOT NULL REFERENCES scope_groups(id) ON DELETE CASCADE,
|
|
258
|
+
member_type TEXT NOT NULL CHECK (member_type IN ('project', 'group')),
|
|
259
|
+
member_id TEXT NOT NULL,
|
|
260
|
+
PRIMARY KEY (group_id, member_type, member_id)
|
|
261
|
+
);
|
|
262
|
+
CREATE INDEX IF NOT EXISTS scope_group_members_group_idx ON scope_group_members(group_id);
|
|
263
|
+
CREATE INDEX IF NOT EXISTS scope_group_members_member_idx ON scope_group_members(member_type, member_id);
|
|
247
264
|
CREATE TABLE IF NOT EXISTS log_sources (
|
|
248
265
|
id TEXT PRIMARY KEY,
|
|
249
266
|
label TEXT NOT NULL,
|
|
@@ -864,7 +881,18 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
864
881
|
// first if no Task ever used it either.
|
|
865
882
|
up: (db) => {
|
|
866
883
|
const existing = new Set((db.prepare("PRAGMA table_info(artifact_scopes)").all() as Array<{ name: string }>).map((row) => row.name));
|
|
867
|
-
|
|
884
|
+
// True only when migrating a genuinely pre-v28 database. A fixture that bootstraps from
|
|
885
|
+
// the CURRENT SCHEMA text (which already declares this column, in whatever its own current
|
|
886
|
+
// CHECK vocabulary is -- version 29 later widens/renames it from 'global'/'projects' to
|
|
887
|
+
// 'none'/'all'/'explicit') and then only fakes an older user_version to exercise an earlier
|
|
888
|
+
// migration's own path already has the column; forcing this migration's literal 'projects'
|
|
889
|
+
// value into it would violate version 29's tighter CHECK once that has shipped. The
|
|
890
|
+
// membership-table backfill below still always runs regardless (idempotent, and never
|
|
891
|
+
// touches the CHECK-restricted mode column itself) -- version 29's own remap pass detects
|
|
892
|
+
// "has a membership row" directly rather than trusting this flag's own mode literal, so a
|
|
893
|
+
// fixture that skips this exact write still ends up in the correct final 'explicit' state.
|
|
894
|
+
const columnPreexisted = existing.has("mode");
|
|
895
|
+
if (!columnPreexisted) {
|
|
868
896
|
db.exec("ALTER TABLE artifact_scopes ADD COLUMN mode TEXT NOT NULL DEFAULT 'global' CHECK (mode IN ('global', 'projects'))");
|
|
869
897
|
}
|
|
870
898
|
db.exec(`
|
|
@@ -893,11 +921,93 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
893
921
|
const now = new Date().toISOString();
|
|
894
922
|
insertProject.run(projectId, basename(row.project_root) || row.project_root, row.project_root, now, now);
|
|
895
923
|
}
|
|
896
|
-
setProjectsMode.run(row.artifact_id);
|
|
924
|
+
if (!columnPreexisted) setProjectsMode.run(row.artifact_id);
|
|
897
925
|
insertMembership.run(row.artifact_id, projectId);
|
|
898
926
|
}
|
|
899
927
|
},
|
|
900
928
|
},
|
|
929
|
+
{
|
|
930
|
+
version: 29,
|
|
931
|
+
name: "artifact-scope-tri-state-and-scope-groups",
|
|
932
|
+
// See scope-group/. Adds the nested-scope-group registry (scope_groups/scope_group_members),
|
|
933
|
+
// and replaces artifact_scopes' two-state mode ('global'|'projects') with a real three-state
|
|
934
|
+
// one ('none'|'all'|'explicit') -- 'none' (fully hidden, never applicable) did not exist
|
|
935
|
+
// before this. SQLite has no ALTER TABLE ... CHECK, so artifact_scopes is rebuilt (copy with
|
|
936
|
+
// remapped mode values, drop, rename) rather than altered in place; every row's own
|
|
937
|
+
// artifact_id/project_root/source/assigned_at is preserved byte-for-byte, only `mode`'s
|
|
938
|
+
// values change (global->all, projects->explicit) and its CHECK constraint widens.
|
|
939
|
+
// artifact_scope_projects is folded into the new, kind-neutral artifact_scope_members
|
|
940
|
+
// (adding member_type so a membership row can name a project OR a scope group) --
|
|
941
|
+
// existing project memberships become member_type='project' rows, unchanged in meaning.
|
|
942
|
+
up: (db) => {
|
|
943
|
+
db.exec(`
|
|
944
|
+
CREATE TABLE IF NOT EXISTS scope_groups (
|
|
945
|
+
id TEXT PRIMARY KEY,
|
|
946
|
+
name TEXT NOT NULL,
|
|
947
|
+
aliases_json TEXT NOT NULL DEFAULT '[]',
|
|
948
|
+
created_at TEXT NOT NULL,
|
|
949
|
+
updated_at TEXT NOT NULL
|
|
950
|
+
);
|
|
951
|
+
CREATE INDEX IF NOT EXISTS scope_groups_name_idx ON scope_groups(name);
|
|
952
|
+
CREATE TABLE IF NOT EXISTS scope_group_members (
|
|
953
|
+
group_id TEXT NOT NULL REFERENCES scope_groups(id) ON DELETE CASCADE,
|
|
954
|
+
member_type TEXT NOT NULL CHECK (member_type IN ('project', 'group')),
|
|
955
|
+
member_id TEXT NOT NULL,
|
|
956
|
+
PRIMARY KEY (group_id, member_type, member_id)
|
|
957
|
+
);
|
|
958
|
+
CREATE INDEX IF NOT EXISTS scope_group_members_group_idx ON scope_group_members(group_id);
|
|
959
|
+
CREATE INDEX IF NOT EXISTS scope_group_members_member_idx ON scope_group_members(member_type, member_id);
|
|
960
|
+
`);
|
|
961
|
+
// Guarded by whether the OLD artifact_scope_projects table still exists, the same class of
|
|
962
|
+
// concern version 16's/note-events' own comments cover: a fixture that bootstraps from the
|
|
963
|
+
// CURRENT SCHEMA text (which already declares artifact_scopes/artifact_scope_members in
|
|
964
|
+
// their POST-this-migration shape) and then only fakes an older user_version to exercise an
|
|
965
|
+
// earlier migration's own path must not attempt to rebuild a table that's already correctly
|
|
966
|
+
// shaped -- rebuilding it again with a CASE mode remap over already-new mode values ('all'/
|
|
967
|
+
// 'explicit') would violate the new CHECK, since those aren't 'global'/'projects' either.
|
|
968
|
+
const oldTableExists =
|
|
969
|
+
db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'artifact_scope_projects'").get() != null;
|
|
970
|
+
if (!oldTableExists) return;
|
|
971
|
+
db.exec(`
|
|
972
|
+
CREATE TABLE artifact_scopes_v29 (
|
|
973
|
+
artifact_id TEXT PRIMARY KEY REFERENCES artifacts(id),
|
|
974
|
+
project_root TEXT,
|
|
975
|
+
mode TEXT NOT NULL DEFAULT 'all' CHECK (mode IN ('none', 'all', 'explicit')),
|
|
976
|
+
source TEXT NOT NULL CHECK (source IN ('cwd', 'explicit', 'unscoped')),
|
|
977
|
+
assigned_at TEXT NOT NULL
|
|
978
|
+
);
|
|
979
|
+
-- Keyed off real membership-row existence, not the old mode literal: v28's own write of
|
|
980
|
+
-- that literal is itself conditionally skipped (see its own comment) when replaying
|
|
981
|
+
-- against a fixture that already has this column in a later, tightened CHECK shape --
|
|
982
|
+
-- membership rows are still always backfilled unconditionally either way, so checking
|
|
983
|
+
-- for one directly is correct in every case, historical or fixture-simulated alike.
|
|
984
|
+
INSERT INTO artifact_scopes_v29 (artifact_id, project_root, mode, source, assigned_at)
|
|
985
|
+
SELECT s.artifact_id, s.project_root,
|
|
986
|
+
CASE
|
|
987
|
+
WHEN EXISTS (SELECT 1 FROM artifact_scope_projects p WHERE p.artifact_id = s.artifact_id) THEN 'explicit'
|
|
988
|
+
WHEN s.mode = 'global' THEN 'all'
|
|
989
|
+
WHEN s.mode = 'projects' THEN 'explicit'
|
|
990
|
+
ELSE s.mode
|
|
991
|
+
END,
|
|
992
|
+
s.source, s.assigned_at
|
|
993
|
+
FROM artifact_scopes s;
|
|
994
|
+
DROP TABLE artifact_scopes;
|
|
995
|
+
ALTER TABLE artifact_scopes_v29 RENAME TO artifact_scopes;
|
|
996
|
+
CREATE INDEX IF NOT EXISTS artifact_scopes_project_idx ON artifact_scopes(project_root, artifact_id);
|
|
997
|
+
|
|
998
|
+
CREATE TABLE IF NOT EXISTS artifact_scope_members (
|
|
999
|
+
artifact_id TEXT NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
|
|
1000
|
+
member_type TEXT NOT NULL CHECK (member_type IN ('project', 'group')),
|
|
1001
|
+
member_id TEXT NOT NULL,
|
|
1002
|
+
PRIMARY KEY (artifact_id, member_type, member_id)
|
|
1003
|
+
);
|
|
1004
|
+
INSERT INTO artifact_scope_members (artifact_id, member_type, member_id)
|
|
1005
|
+
SELECT artifact_id, 'project', project_id FROM artifact_scope_projects;
|
|
1006
|
+
DROP TABLE artifact_scope_projects;
|
|
1007
|
+
CREATE INDEX IF NOT EXISTS artifact_scope_members_member_idx ON artifact_scope_members(member_type, member_id, artifact_id);
|
|
1008
|
+
`);
|
|
1009
|
+
},
|
|
1010
|
+
},
|
|
901
1011
|
];
|
|
902
1012
|
|
|
903
1013
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendDiscussionRound, DiscussionRound, DiscussionRoundQuery } from "../
|
|
1
|
+
import type { AppendDiscussionRound, DiscussionRound, DiscussionRoundQuery } from "../discussion/discussion.ts";
|
|
2
2
|
|
|
3
3
|
/** Persistence port for a Discussion's append-only rounds (see domain/discussion.ts). */
|
|
4
4
|
export interface DiscussionRoundStore {
|
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
validateDiscussionOptions,
|
|
22
22
|
validateSelectedOptions,
|
|
23
23
|
validateSettlement,
|
|
24
|
-
} from "
|
|
25
|
-
import type { DiscussionRoundStore } from "
|
|
24
|
+
} from "./discussion.ts";
|
|
25
|
+
import type { DiscussionRoundStore } from "./discussion-round-store.ts";
|
|
26
26
|
|
|
27
27
|
export class DiscussionError extends Error {}
|
|
28
28
|
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
validateDiscussionActor,
|
|
9
9
|
validateDiscussionContent,
|
|
10
10
|
validateDiscussionOptions,
|
|
11
|
-
} from "../
|
|
12
|
-
import type { DiscussionRoundStore } from "
|
|
11
|
+
} from "../discussion/discussion.ts";
|
|
12
|
+
import type { DiscussionRoundStore } from "../discussion/discussion-round-store.ts";
|
|
13
13
|
|
|
14
14
|
interface DiscussionRoundRow {
|
|
15
15
|
id: number;
|
package/src/docs/docs-service.ts
CHANGED
|
@@ -9,23 +9,29 @@ import type { ArtifactEventContext } from "../artifact/artifact-event.ts";
|
|
|
9
9
|
import type { ArtifactScope, ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
|
|
10
10
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
11
11
|
import type { ArtifactAction, AuthorityRegistry } from "../authority-registry.ts";
|
|
12
|
-
import { ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT } from "../constants.ts";
|
|
13
|
-
import { resolveProjectReference } from "../domain/project-registry.ts";
|
|
14
|
-
import { normalizeProjectRoot } from "../domain/task-scope.ts";
|
|
15
12
|
import {
|
|
13
|
+
addArtifactScopeGroup,
|
|
14
|
+
addArtifactScopeProject,
|
|
16
15
|
assertBodyBounds,
|
|
17
16
|
assertLabelsBounds,
|
|
18
17
|
assertTitleBounds,
|
|
19
18
|
type ListFilter,
|
|
20
19
|
listScoped,
|
|
20
|
+
removeArtifactScopeGroup,
|
|
21
|
+
removeArtifactScopeProject,
|
|
22
|
+
replaceArtifactScopeGroups,
|
|
23
|
+
replaceArtifactScopeProjects,
|
|
21
24
|
requireContentUpdateFields,
|
|
22
25
|
requireKind,
|
|
23
26
|
runTransition,
|
|
27
|
+
setArtifactScopeNone,
|
|
24
28
|
type TransitionTable,
|
|
25
29
|
type UpdateContentInput,
|
|
26
30
|
} from "../domain-service-shared.ts";
|
|
27
31
|
import { NOTE_SUBTYPE } from "../note/note-service.ts";
|
|
28
|
-
import type { ProjectRegistryStore } from "../
|
|
32
|
+
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
33
|
+
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
34
|
+
import { normalizeProjectRoot } from "../task-scope/task-scope.ts";
|
|
29
35
|
|
|
30
36
|
function rejectsNoteTemplate(artifacts: ArtifactStore, templateId: string | undefined, subtype: string | undefined): boolean {
|
|
31
37
|
if (subtype === NOTE_SUBTYPE) return true;
|
|
@@ -117,11 +123,7 @@ export function createDocument(
|
|
|
117
123
|
context,
|
|
118
124
|
);
|
|
119
125
|
if (input.projectReferences !== undefined && input.projectReferences.length > 0) {
|
|
120
|
-
|
|
121
|
-
throw new Error(`projectReferences may include at most ${ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT} entries`);
|
|
122
|
-
}
|
|
123
|
-
const ids = input.projectReferences.map((reference) => resolveProjectReference(registry!, reference).id);
|
|
124
|
-
scopes.replaceProjects(document.id, ids, "explicit");
|
|
126
|
+
replaceArtifactScopeProjects(scopes, registry!, document.id, input.projectReferences);
|
|
125
127
|
} else {
|
|
126
128
|
scopes.assign(document.id, projectRoot, projectRoot === undefined ? "unscoped" : "explicit");
|
|
127
129
|
}
|
|
@@ -163,7 +165,13 @@ export function docScope(artifacts: ArtifactStore, scopes: ArtifactScopeStore, i
|
|
|
163
165
|
|
|
164
166
|
export function setDocGlobal(artifacts: ArtifactStore, scopes: ArtifactScopeStore, id: string): ArtifactScope {
|
|
165
167
|
requireDocument(artifacts, id);
|
|
166
|
-
return scopes.
|
|
168
|
+
return scopes.setAll(id, "explicit");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Fully hides this Doc -- never applicable, never context-injected, regardless of project. The only new mode beyond the pre-existing global/projects pair. */
|
|
172
|
+
export function setDocNone(artifacts: ArtifactStore, scopes: ArtifactScopeStore, id: string): ArtifactScope {
|
|
173
|
+
requireDocument(artifacts, id);
|
|
174
|
+
return setArtifactScopeNone(scopes, id);
|
|
167
175
|
}
|
|
168
176
|
|
|
169
177
|
export function replaceDocProjects(
|
|
@@ -174,12 +182,7 @@ export function replaceDocProjects(
|
|
|
174
182
|
projectReferences: readonly string[],
|
|
175
183
|
): ArtifactScope {
|
|
176
184
|
requireDocument(artifacts, id);
|
|
177
|
-
|
|
178
|
-
if (projectReferences.length > ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT) {
|
|
179
|
-
throw new Error(`projectReferences may include at most ${ARTIFACT_SCOPE_MAX_PROJECTS_PER_ARTIFACT} entries`);
|
|
180
|
-
}
|
|
181
|
-
const ids = projectReferences.map((reference) => resolveProjectReference(registry, reference).id);
|
|
182
|
-
return scopes.replaceProjects(id, ids, "explicit");
|
|
185
|
+
return replaceArtifactScopeProjects(scopes, registry, id, projectReferences);
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
export function addDocProject(
|
|
@@ -190,8 +193,7 @@ export function addDocProject(
|
|
|
190
193
|
projectReference: string,
|
|
191
194
|
): ArtifactScope {
|
|
192
195
|
requireDocument(artifacts, id);
|
|
193
|
-
|
|
194
|
-
return scopes.addProject(id, project.id, "explicit");
|
|
196
|
+
return addArtifactScopeProject(scopes, registry, id, projectReference);
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
export function removeDocProject(
|
|
@@ -202,8 +204,41 @@ export function removeDocProject(
|
|
|
202
204
|
projectReference: string,
|
|
203
205
|
): ArtifactScope {
|
|
204
206
|
requireDocument(artifacts, id);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
+
return removeArtifactScopeProject(scopes, registry, id, projectReference);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Scope-group ('nested scope') siblings of replaceDocProjects/addDocProject/removeDocProject -- same Note-rejecting Doc guard, delegating the actual reference-resolution and store call to the shared, kind-agnostic helpers. */
|
|
211
|
+
export function replaceDocGroups(
|
|
212
|
+
artifacts: ArtifactStore,
|
|
213
|
+
scopes: ArtifactScopeStore,
|
|
214
|
+
scopeGroups: ScopeGroupStore,
|
|
215
|
+
id: string,
|
|
216
|
+
groupReferences: readonly string[],
|
|
217
|
+
): ArtifactScope {
|
|
218
|
+
requireDocument(artifacts, id);
|
|
219
|
+
return replaceArtifactScopeGroups(scopes, scopeGroups, id, groupReferences);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function addDocGroup(
|
|
223
|
+
artifacts: ArtifactStore,
|
|
224
|
+
scopes: ArtifactScopeStore,
|
|
225
|
+
scopeGroups: ScopeGroupStore,
|
|
226
|
+
id: string,
|
|
227
|
+
groupReference: string,
|
|
228
|
+
): ArtifactScope {
|
|
229
|
+
requireDocument(artifacts, id);
|
|
230
|
+
return addArtifactScopeGroup(scopes, scopeGroups, id, groupReference);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function removeDocGroup(
|
|
234
|
+
artifacts: ArtifactStore,
|
|
235
|
+
scopes: ArtifactScopeStore,
|
|
236
|
+
scopeGroups: ScopeGroupStore,
|
|
237
|
+
id: string,
|
|
238
|
+
groupReference: string,
|
|
239
|
+
): ArtifactScope {
|
|
240
|
+
requireDocument(artifacts, id);
|
|
241
|
+
return removeArtifactScopeGroup(scopes, scopeGroups, id, groupReference);
|
|
207
242
|
}
|
|
208
243
|
|
|
209
244
|
function requireDocument(artifacts: ArtifactStore, id: string): Artifact {
|
|
@@ -7,16 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Artifact } from "./artifact/artifact.ts";
|
|
9
9
|
import type { ArtifactEventContext } from "./artifact/artifact-event.ts";
|
|
10
|
-
import type { ArtifactScopeStore } from "./artifact/artifact-scope-store.ts";
|
|
10
|
+
import type { ArtifactScope, ArtifactScopeStore } from "./artifact/artifact-scope-store.ts";
|
|
11
11
|
import type { ArtifactStore } from "./artifact/artifact-store.ts";
|
|
12
12
|
import {
|
|
13
13
|
ARTIFACT_BODY_MAX_LENGTH,
|
|
14
14
|
ARTIFACT_LABEL_MAX_COUNT,
|
|
15
15
|
ARTIFACT_LABEL_MAX_LENGTH,
|
|
16
16
|
ARTIFACT_SCOPE_MAX_ARTIFACTS,
|
|
17
|
+
ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT,
|
|
17
18
|
ARTIFACT_TITLE_MAX_LENGTH,
|
|
18
19
|
} from "./constants.ts";
|
|
19
|
-
import {
|
|
20
|
+
import { resolveProjectReference } from "./project-registry/project-registry.ts";
|
|
21
|
+
import type { ProjectRegistryStore } from "./project-registry/project-registry-store.ts";
|
|
22
|
+
import { resolveScopeGroupReference } from "./scope-group/scope-group.ts";
|
|
23
|
+
import type { ScopeGroupStore } from "./scope-group/scope-group-store.ts";
|
|
24
|
+
import { normalizeProjectRoot } from "./task-scope/task-scope.ts";
|
|
20
25
|
|
|
21
26
|
export interface UpdateContentInput {
|
|
22
27
|
title?: string;
|
|
@@ -181,3 +186,86 @@ export function assignArtifactProject(
|
|
|
181
186
|
);
|
|
182
187
|
return artifacts.get(id)!;
|
|
183
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Kind-agnostic scope-mutation primitives shared by docs/rules/playbook-service.ts's own thin,
|
|
192
|
+
* per-kind wrappers (each of which validates the artifact's own kind/subtype first -- e.g.
|
|
193
|
+
* requireDocument's Note exclusion -- then delegates here for the actual reference-resolution +
|
|
194
|
+
* bounds-check + store-call, which is genuinely identical across every kind). Mirrors
|
|
195
|
+
* assignArtifactProject's own already-established split, extended to the tri-state
|
|
196
|
+
* none/all/explicit model and to scope groups ('nested scopes').
|
|
197
|
+
*/
|
|
198
|
+
export function setArtifactScopeNone(scopes: ArtifactScopeStore, artifactId: string): ArtifactScope {
|
|
199
|
+
return scopes.setNone(artifactId, "explicit");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function replaceArtifactScopeProjects(
|
|
203
|
+
scopes: ArtifactScopeStore,
|
|
204
|
+
registry: ProjectRegistryStore,
|
|
205
|
+
artifactId: string,
|
|
206
|
+
projectReferences: readonly string[],
|
|
207
|
+
): ArtifactScope {
|
|
208
|
+
if (projectReferences.length === 0) throw new Error("projectReferences must be non-empty; use set_none/set_global to clear scoping");
|
|
209
|
+
if (projectReferences.length > ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
|
|
210
|
+
throw new Error(`projectReferences may include at most ${ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT} entries`);
|
|
211
|
+
}
|
|
212
|
+
const members = projectReferences.map((reference) => ({ type: "project" as const, id: resolveProjectReference(registry, reference).id }));
|
|
213
|
+
return scopes.replaceMembers(artifactId, members, "explicit");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function addArtifactScopeProject(
|
|
217
|
+
scopes: ArtifactScopeStore,
|
|
218
|
+
registry: ProjectRegistryStore,
|
|
219
|
+
artifactId: string,
|
|
220
|
+
projectReference: string,
|
|
221
|
+
): ArtifactScope {
|
|
222
|
+
const project = resolveProjectReference(registry, projectReference);
|
|
223
|
+
return scopes.addMember(artifactId, { type: "project", id: project.id }, "explicit");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function removeArtifactScopeProject(
|
|
227
|
+
scopes: ArtifactScopeStore,
|
|
228
|
+
registry: ProjectRegistryStore,
|
|
229
|
+
artifactId: string,
|
|
230
|
+
projectReference: string,
|
|
231
|
+
): ArtifactScope {
|
|
232
|
+
const project = resolveProjectReference(registry, projectReference);
|
|
233
|
+
return scopes.removeMember(artifactId, { type: "project", id: project.id });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function replaceArtifactScopeGroups(
|
|
237
|
+
scopes: ArtifactScopeStore,
|
|
238
|
+
scopeGroups: ScopeGroupStore,
|
|
239
|
+
artifactId: string,
|
|
240
|
+
groupReferences: readonly string[],
|
|
241
|
+
): ArtifactScope {
|
|
242
|
+
if (groupReferences.length === 0) throw new Error("groupReferences must be non-empty; use set_none/set_global to clear scoping");
|
|
243
|
+
if (groupReferences.length > ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT) {
|
|
244
|
+
throw new Error(`groupReferences may include at most ${ARTIFACT_SCOPE_MAX_MEMBERS_PER_ARTIFACT} entries`);
|
|
245
|
+
}
|
|
246
|
+
const members = groupReferences.map((reference) => ({
|
|
247
|
+
type: "group" as const,
|
|
248
|
+
id: resolveScopeGroupReference(scopeGroups, reference).id,
|
|
249
|
+
}));
|
|
250
|
+
return scopes.replaceMembers(artifactId, members, "explicit");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function addArtifactScopeGroup(
|
|
254
|
+
scopes: ArtifactScopeStore,
|
|
255
|
+
scopeGroups: ScopeGroupStore,
|
|
256
|
+
artifactId: string,
|
|
257
|
+
groupReference: string,
|
|
258
|
+
): ArtifactScope {
|
|
259
|
+
const group = resolveScopeGroupReference(scopeGroups, groupReference);
|
|
260
|
+
return scopes.addMember(artifactId, { type: "group", id: group.id }, "explicit");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function removeArtifactScopeGroup(
|
|
264
|
+
scopes: ArtifactScopeStore,
|
|
265
|
+
scopeGroups: ScopeGroupStore,
|
|
266
|
+
artifactId: string,
|
|
267
|
+
groupReference: string,
|
|
268
|
+
): ArtifactScope {
|
|
269
|
+
const group = resolveScopeGroupReference(scopeGroups, groupReference);
|
|
270
|
+
return scopes.removeMember(artifactId, { type: "group", id: group.id });
|
|
271
|
+
}
|
|
@@ -26,8 +26,8 @@ import {
|
|
|
26
26
|
GATE_TEST_TIMEOUT_MS,
|
|
27
27
|
} from "../constants.ts";
|
|
28
28
|
import type { Db } from "../db.ts";
|
|
29
|
+
import type { Gate, GateResult, GateRunOptions } from "../gate/gate.ts";
|
|
29
30
|
import { getArtifact } from "../ops.ts";
|
|
30
|
-
import type { Gate, GateResult, GateRunOptions } from "./gate.ts";
|
|
31
31
|
|
|
32
32
|
const require_ = createRequire(import.meta.url);
|
|
33
33
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Db } from "../db.ts";
|
|
2
|
-
import type { GateResult, GateRunOptions } from "../
|
|
3
|
-
import { runGates, runGatesAsync } from "../
|
|
4
|
-
import type { GateRunner } from "
|
|
2
|
+
import type { GateResult, GateRunOptions } from "../gate/gate.ts";
|
|
3
|
+
import { runGates, runGatesAsync } from "../gate/gate-execution.ts";
|
|
4
|
+
import type { GateRunner } from "../gate/gate-runner.ts";
|
|
5
5
|
|
|
6
6
|
export class SQLiteGateRunner implements GateRunner {
|
|
7
7
|
constructor(private readonly db: Db) {}
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
GRAPH_PROJECTION_MAX_ARTIFACTS_PER_BATCH,
|
|
13
13
|
GRAPH_PROJECTION_MAX_EDGES_PER_BATCH,
|
|
14
14
|
} from "../constants.ts";
|
|
15
|
-
import { GRAPH_PROJECTION_SCHEMA_VERSION, type GraphProjectionBatch, type GraphProjectionResult } from "
|
|
16
|
-
import type { GraphProjectionStore } from "
|
|
15
|
+
import { GRAPH_PROJECTION_SCHEMA_VERSION, type GraphProjectionBatch, type GraphProjectionResult } from "./graph-projection.ts";
|
|
16
|
+
import type { GraphProjectionStore } from "./graph-projection-store.ts";
|
|
17
17
|
|
|
18
18
|
function boundedId(value: string, label: string): string {
|
|
19
19
|
if (!value || value.length === 0) throw new Error(`${label} is required`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Db } from "../db.ts";
|
|
2
2
|
import { inTransaction } from "../db.ts";
|
|
3
|
-
import type { ProjectionCheckpoint } from "../
|
|
4
|
-
import type { GraphProjectionStore } from "
|
|
3
|
+
import type { ProjectionCheckpoint } from "../graph-projection/graph-projection.ts";
|
|
4
|
+
import type { GraphProjectionStore } from "../graph-projection/graph-projection-store.ts";
|
|
5
5
|
|
|
6
6
|
export class SQLiteGraphProjectionStore implements GraphProjectionStore {
|
|
7
7
|
constructor(private readonly db: Db) {}
|
package/src/handlers/discuss.ts
CHANGED
|
@@ -18,8 +18,8 @@ import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
|
18
18
|
import type { Artifact } from "../artifact/artifact.ts";
|
|
19
19
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
20
20
|
import { DISCUSSION_OPTION_DESCRIPTION_MAX_LENGTH, DISCUSSION_OPTION_MAX_LENGTH, DISCUSSION_OPTIONS_MAX_COUNT } from "../constants.ts";
|
|
21
|
+
import { DISCUSSION_SUBTYPE, type DiscussionRound } from "../discussion/discussion.ts";
|
|
21
22
|
import type { DiscussionAndRounds, Discussions } from "../discussion/discussion-service.ts";
|
|
22
|
-
import { DISCUSSION_SUBTYPE, type DiscussionRound } from "../domain/discussion.ts";
|
|
23
23
|
import { discussOperations } from "../modules/discuss.ts";
|
|
24
24
|
import {
|
|
25
25
|
createOperationDefiner,
|
package/src/handlers/docs.ts
CHANGED
|
@@ -9,7 +9,8 @@ import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
|
9
9
|
import type { AuthorityRegistry } from "../authority-registry.ts";
|
|
10
10
|
import { listDocuments } from "../docs/docs-service.ts";
|
|
11
11
|
import { docsOperations } from "../modules/docs.ts";
|
|
12
|
-
import type { ProjectRegistryStore } from "../
|
|
12
|
+
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
|
|
13
|
+
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
|
|
13
14
|
import { booleanProp, createOperationDefiner, numberProp, resolveArtifactIdWidened, stringProp, validationError } from "./shared.ts";
|
|
14
15
|
|
|
15
16
|
const OWNER = "docs";
|
|
@@ -54,8 +55,9 @@ export function registerDocsVehicleOperations(
|
|
|
54
55
|
scopes: ArtifactScopeStore,
|
|
55
56
|
authority: AuthorityRegistry,
|
|
56
57
|
projectRegistry: ProjectRegistryStore,
|
|
58
|
+
scopeGroups: ScopeGroupStore,
|
|
57
59
|
): void {
|
|
58
|
-
const moduleOperations = new Map(docsOperations(artifacts, scopes, authority, projectRegistry).map((op) => [op.name, op]));
|
|
60
|
+
const moduleOperations = new Map(docsOperations(artifacts, scopes, authority, projectRegistry, scopeGroups).map((op) => [op.name, op]));
|
|
59
61
|
const call = (name: string, input: Record<string, unknown>): unknown => moduleOperations.get(name)!.execute(input);
|
|
60
62
|
const define = createOperationDefiner(registry, OWNER, "docs", ["docs:read", "docs:write"], call);
|
|
61
63
|
|
|
@@ -209,6 +211,59 @@ export function registerDocsVehicleOperations(
|
|
|
209
211
|
(input) => ({ ...input, id: resolveDocId(artifacts, scopes, input.project_root as string | undefined, input.id, input.name) }),
|
|
210
212
|
);
|
|
211
213
|
|
|
214
|
+
define(
|
|
215
|
+
"set_none",
|
|
216
|
+
"Fully hides a Doc -- never applicable, never context-injected, regardless of project. The only way back is set_global, add_project, add_group, or replace_projects/replace_groups.",
|
|
217
|
+
"local-write",
|
|
218
|
+
{ id: stringProp, name: stringProp, project_root: stringProp },
|
|
219
|
+
[],
|
|
220
|
+
(input) => ({ ...input, id: resolveDocId(artifacts, scopes, input.project_root as string | undefined, input.id, input.name) }),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
define(
|
|
224
|
+
"add_group",
|
|
225
|
+
"Adds one scope group (a named, reusable, possibly-nested collection of projects and/or other groups) to a Doc's explicit scope, switching it from global/none to project-bound if it wasn't already. Idempotent if the group is already a member.",
|
|
226
|
+
"local-write",
|
|
227
|
+
{
|
|
228
|
+
id: stringProp,
|
|
229
|
+
name: stringProp,
|
|
230
|
+
group: { ...stringProp, description: "Exact scope group id, name, or alias to add." },
|
|
231
|
+
project_root: stringProp,
|
|
232
|
+
},
|
|
233
|
+
["group"],
|
|
234
|
+
(input) => ({ ...input, id: resolveDocId(artifacts, scopes, input.project_root as string | undefined, input.id, input.name) }),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
define(
|
|
238
|
+
"remove_group",
|
|
239
|
+
"Removes one scope group from a Doc's explicit scope. Rejected while it is the Doc's only remaining scope member -- call set_global or set_none first.",
|
|
240
|
+
"local-write",
|
|
241
|
+
{
|
|
242
|
+
id: stringProp,
|
|
243
|
+
name: stringProp,
|
|
244
|
+
group: { ...stringProp, description: "Exact scope group id, name, or alias to remove." },
|
|
245
|
+
project_root: stringProp,
|
|
246
|
+
},
|
|
247
|
+
["group"],
|
|
248
|
+
(input) => ({ ...input, id: resolveDocId(artifacts, scopes, input.project_root as string | undefined, input.id, input.name) }),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
define(
|
|
252
|
+
"replace_groups",
|
|
253
|
+
"Replaces a Doc's entire scope-group membership with exactly this bounded, non-empty list of scope group references (id/name/alias). Use set_global/set_none instead to clear scoping entirely.",
|
|
254
|
+
"local-write",
|
|
255
|
+
{
|
|
256
|
+
id: stringProp,
|
|
257
|
+
name: stringProp,
|
|
258
|
+
groups: { type: "array", description: "Non-empty list of exact scope group id/name/alias references." } as unknown as {
|
|
259
|
+
type: string;
|
|
260
|
+
},
|
|
261
|
+
project_root: stringProp,
|
|
262
|
+
},
|
|
263
|
+
["groups"],
|
|
264
|
+
(input) => ({ ...input, id: resolveDocId(artifacts, scopes, input.project_root as string | undefined, input.id, input.name) }),
|
|
265
|
+
);
|
|
266
|
+
|
|
212
267
|
define(
|
|
213
268
|
"update",
|
|
214
269
|
"Changes a Doc's title/body/labels (at least one required). Refused for a read-only external projection (e.g. web-spider-ingested Docs) -- capture a correction as a new linked Doc instead.",
|