@opengeni/api-router 0.17.0 → 0.21.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.
@@ -2,11 +2,14 @@ import {
2
2
  WORKSPACE_STATE_MAX_BASES,
3
3
  WORKSPACE_STATE_MAX_TOPICS,
4
4
  WORKSPACE_STATE_TOPIC_MAX_CHARS,
5
+ WorkspaceStateQuery,
5
6
  WorkspaceStateResponse,
6
7
  } from "@opengeni/contracts";
7
8
  import { hasPermission, requireAccessGrant, type ApiRouteDeps } from "@opengeni/core";
8
9
  import {
9
10
  getWorkspace,
11
+ getCurrentPreferenceRegistryGovernanceMetadata,
12
+ getWorkspaceStateAcceptedAttemptGovernance,
10
13
  listWorkspaceStateMemoryRecords,
11
14
  listWorkspaceInstructionPolicyRevisions,
12
15
  } from "@opengeni/db";
@@ -19,11 +22,12 @@ import { projectWorkspaceState } from "../workspace-state-projection";
19
22
  export function registerWorkspaceStateRoutes(app: Hono, deps: ApiRouteDeps): void {
20
23
  app.get("/v1/workspaces/:workspaceId/workspace-state", async (context) => {
21
24
  const workspaceId = context.req.param("workspaceId");
25
+ const query = WorkspaceStateQuery.parse(context.req.query());
22
26
  const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
23
27
  const generatedAt = new Date().toISOString();
24
28
  const canInspectKnowledge = hasPermission(grant.permissions, "documents:search");
25
29
 
26
- const [workspace, policies, knowledge] = await Promise.all([
30
+ const [workspace, policies, knowledge, attemptGovernance] = await Promise.all([
27
31
  getWorkspace(deps.db, workspaceId),
28
32
  listWorkspaceInstructionPolicyRevisions(deps.db, workspaceId, { limit: 1 }),
29
33
  canInspectKnowledge
@@ -40,6 +44,46 @@ export function registerWorkspaceStateRoutes(app: Hono, deps: ApiRouteDeps): voi
40
44
  return { documents, memories };
41
45
  })()
42
46
  : Promise.resolve(null),
47
+ query.attemptId
48
+ ? getWorkspaceStateAcceptedAttemptGovernance(deps.db, {
49
+ accountId: grant.accountId,
50
+ workspaceId,
51
+ subjectId: grant.subjectId,
52
+ attemptId: query.attemptId,
53
+ }).then(async (snapshot) => {
54
+ if (!snapshot) return { status: "unavailable" as const };
55
+ const currentPreferences = await getCurrentPreferenceRegistryGovernanceMetadata(
56
+ deps.db,
57
+ {
58
+ workspaceId,
59
+ subjectId: grant.subjectId,
60
+ },
61
+ );
62
+ return {
63
+ status: "available" as const,
64
+ attemptId: snapshot.attemptId,
65
+ executionGeneration: snapshot.executionGeneration,
66
+ acceptedAt: snapshot.acceptedAt,
67
+ policySnapshot: snapshot.policySnapshot,
68
+ preferenceSnapshot: snapshot.preferenceSnapshot
69
+ ? {
70
+ id: snapshot.preferenceSnapshot.id,
71
+ descriptorHash: snapshot.preferenceSnapshot.descriptorHash,
72
+ descriptors: snapshot.preferenceSnapshot.descriptors.map((descriptor) => ({
73
+ id: descriptor.id,
74
+ revisionId: descriptor.revisionId,
75
+ contentHash: descriptor.contentHash,
76
+ activeVersion: descriptor.activeVersion,
77
+ scope: descriptor.scope,
78
+ })),
79
+ truncated: snapshot.preferenceSnapshot.truncated,
80
+ createdAt: snapshot.preferenceSnapshot.createdAt,
81
+ }
82
+ : null,
83
+ currentPreferences,
84
+ };
85
+ })
86
+ : Promise.resolve(null),
43
87
  ]);
44
88
 
45
89
  if (!workspace) {
@@ -54,6 +98,7 @@ export function registerWorkspaceStateRoutes(app: Hono, deps: ApiRouteDeps): voi
54
98
  workspaceAgentInstructions: workspace.agentInstructions,
55
99
  policies,
56
100
  knowledge,
101
+ attemptGovernance,
57
102
  }),
58
103
  ),
59
104
  );
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import {
2
3
  KnowledgeMemoryKind,
3
4
  KnowledgeMemoryStatus,
@@ -10,6 +11,8 @@ import {
10
11
  WORKSPACE_STATE_TOPIC_MAX_CHARS,
11
12
  WorkspaceStateResponse,
12
13
  type WorkspaceInstructionPolicyListResponse,
14
+ type WorkspaceInstructionPolicySnapshot,
15
+ type WorkspaceStateGovernanceDriftStatus,
13
16
  type WorkspaceStateGap,
14
17
  type WorkspaceStateMemoryKindCounts,
15
18
  type WorkspaceStateMemoryStatusCounts,
@@ -23,14 +26,213 @@ type KnowledgeProjectionInput = {
23
26
  memories: WorkspaceStateMemoryRecord[];
24
27
  };
25
28
 
29
+ type PreferenceGovernanceIdentity = {
30
+ id: string;
31
+ revisionId: string;
32
+ contentHash: string;
33
+ activeVersion: number;
34
+ scope: "organization" | "workspace" | "user";
35
+ };
36
+
37
+ type AttemptGovernanceProjectionInput =
38
+ | { status: "unavailable" }
39
+ | {
40
+ status: "available";
41
+ attemptId: string;
42
+ executionGeneration: number;
43
+ acceptedAt: string;
44
+ policySnapshot: WorkspaceInstructionPolicySnapshot | null;
45
+ preferenceSnapshot: {
46
+ id: string;
47
+ descriptorHash: string;
48
+ descriptors: PreferenceGovernanceIdentity[];
49
+ truncated: boolean;
50
+ createdAt: string;
51
+ } | null;
52
+ currentPreferences: {
53
+ descriptors: PreferenceGovernanceIdentity[];
54
+ truncated: boolean;
55
+ };
56
+ };
57
+
26
58
  export type WorkspaceStateProjectionInput = {
27
59
  workspaceId: string;
28
60
  generatedAt: string;
29
61
  workspaceAgentInstructions: string | null;
30
62
  policies: WorkspaceInstructionPolicyListResponse;
31
63
  knowledge: KnowledgeProjectionInput | null;
64
+ attemptGovernance?: AttemptGovernanceProjectionInput | null;
32
65
  };
33
66
 
67
+ function hashIdentities(values: readonly string[]): string {
68
+ return createHash("sha256").update(values.join("\n"), "utf8").digest("hex");
69
+ }
70
+
71
+ function policyTargetKey(value: { kind: string; scope: string; roleKey: string | null }): string {
72
+ return `${value.kind}:${value.scope}:${value.roleKey ?? ""}`;
73
+ }
74
+
75
+ function policyTargetKeysForRole(policyRole: string | null): Set<string> {
76
+ const keys = new Set(["charter:global:", "policy:global:"]);
77
+ if (policyRole !== null) keys.add(`policy:role:${policyRole}`);
78
+ return keys;
79
+ }
80
+
81
+ function policyIdentity(value: {
82
+ kind: string;
83
+ scope: string;
84
+ roleKey: string | null;
85
+ revisionId: string;
86
+ contentHash: string;
87
+ activationVersion: number;
88
+ }): string {
89
+ return `${policyTargetKey(value)}:${value.revisionId}:${value.contentHash}:${value.activationVersion}`;
90
+ }
91
+
92
+ function preferenceIdentity(value: PreferenceGovernanceIdentity): string {
93
+ return `${value.scope}:${value.id}:${value.revisionId}:${value.contentHash}:${value.activeVersion}`;
94
+ }
95
+
96
+ function classifyIdentityDrift(
97
+ snapshotIdentities: readonly string[],
98
+ currentIdentities: readonly string[],
99
+ snapshotKeys: readonly string[],
100
+ currentKeys: readonly string[],
101
+ ): "identical" | "changed" | "superseded" {
102
+ if (snapshotIdentities.join("\n") === currentIdentities.join("\n")) return "identical";
103
+ return snapshotKeys.join("\n") === currentKeys.join("\n") ? "superseded" : "changed";
104
+ }
105
+
106
+ function overallDriftStatus(
107
+ policy: WorkspaceStateGovernanceDriftStatus,
108
+ preferences: WorkspaceStateGovernanceDriftStatus,
109
+ ): WorkspaceStateGovernanceDriftStatus {
110
+ for (const status of ["unavailable", "truncated", "missing", "changed", "superseded"] as const) {
111
+ if (policy === status || preferences === status) return status;
112
+ }
113
+ return "identical";
114
+ }
115
+
116
+ function attemptGovernanceProjection(input: WorkspaceStateProjectionInput) {
117
+ const governance = input.attemptGovernance ?? null;
118
+ if (governance === null) return { status: "not_requested" as const };
119
+ if (governance.status === "unavailable") {
120
+ return {
121
+ status: "unavailable" as const,
122
+ reason: "attempt_not_found_or_not_authorized" as const,
123
+ driftStatus: "unavailable" as const,
124
+ };
125
+ }
126
+
127
+ const policySnapshot = governance.policySnapshot;
128
+ let policyStatus: WorkspaceStateGovernanceDriftStatus = "missing";
129
+ let policySnapshotHash: string | null = null;
130
+ let policyCurrentHash: string | null = null;
131
+ let policySnapshotTargetCount = 0;
132
+ let policyCurrentTargetCount = 0;
133
+ if (policySnapshot) {
134
+ const snapshotEntries = [...policySnapshot.entries].sort((left, right) =>
135
+ policyTargetKey(left).localeCompare(policyTargetKey(right)),
136
+ );
137
+ const snapshotKeys = snapshotEntries.map(policyTargetKey);
138
+ const relevantTargetKeys = policyTargetKeysForRole(policySnapshot.policyRole);
139
+ const currentEntries = input.policies.activeHeads
140
+ .filter((head) => relevantTargetKeys.has(policyTargetKey(head)))
141
+ .sort((left, right) => policyTargetKey(left).localeCompare(policyTargetKey(right)));
142
+ const snapshotIdentities = snapshotEntries.map(policyIdentity);
143
+ const currentIdentities = currentEntries.map(policyIdentity);
144
+ const currentKeys = currentEntries.map(policyTargetKey);
145
+ policyStatus = classifyIdentityDrift(
146
+ snapshotIdentities,
147
+ currentIdentities,
148
+ snapshotKeys,
149
+ currentKeys,
150
+ );
151
+ policySnapshotHash = hashIdentities(snapshotIdentities);
152
+ policyCurrentHash = hashIdentities(currentIdentities);
153
+ policySnapshotTargetCount = snapshotEntries.length;
154
+ policyCurrentTargetCount = currentEntries.length;
155
+ }
156
+
157
+ const preferenceSnapshot = governance.preferenceSnapshot;
158
+ const currentPreferences = [...governance.currentPreferences.descriptors].sort((left, right) =>
159
+ preferenceIdentity(left).localeCompare(preferenceIdentity(right)),
160
+ );
161
+ let preferenceStatus: WorkspaceStateGovernanceDriftStatus = "missing";
162
+ let preferenceSnapshotHash: string | null = null;
163
+ const currentPreferenceIdentities = currentPreferences.map(preferenceIdentity);
164
+ const currentPreferenceHash = hashIdentities(currentPreferenceIdentities);
165
+ let snapshotPreferenceCount = 0;
166
+ let snapshotPreferenceTruncated = false;
167
+ if (preferenceSnapshot) {
168
+ const snapshotPreferences = [...preferenceSnapshot.descriptors].sort((left, right) =>
169
+ preferenceIdentity(left).localeCompare(preferenceIdentity(right)),
170
+ );
171
+ const snapshotPreferenceIdentities = snapshotPreferences.map(preferenceIdentity);
172
+ const snapshotPreferenceKeys = snapshotPreferences.map((descriptor) => descriptor.id).sort();
173
+ const currentPreferenceKeys = currentPreferences.map((descriptor) => descriptor.id).sort();
174
+ preferenceSnapshotHash = hashIdentities(snapshotPreferenceIdentities);
175
+ snapshotPreferenceCount = snapshotPreferences.length;
176
+ snapshotPreferenceTruncated = preferenceSnapshot.truncated;
177
+ preferenceStatus =
178
+ preferenceSnapshot.truncated || governance.currentPreferences.truncated
179
+ ? "truncated"
180
+ : classifyIdentityDrift(
181
+ snapshotPreferenceIdentities,
182
+ currentPreferenceIdentities,
183
+ snapshotPreferenceKeys,
184
+ currentPreferenceKeys,
185
+ );
186
+ }
187
+
188
+ return {
189
+ status: "available" as const,
190
+ attemptId: governance.attemptId,
191
+ executionGeneration: governance.executionGeneration,
192
+ acceptedAt: governance.acceptedAt,
193
+ policySnapshot: policySnapshot
194
+ ? {
195
+ status: "available" as const,
196
+ id: policySnapshot.id,
197
+ createdAt: policySnapshot.createdAt,
198
+ entryHash: policySnapshot.entryHash,
199
+ policyRole: policySnapshot.policyRole,
200
+ roleSource: policySnapshot.roleSource,
201
+ entries: policySnapshot.entries,
202
+ }
203
+ : { status: "missing" as const },
204
+ preferenceSnapshot: preferenceSnapshot
205
+ ? {
206
+ status: "available" as const,
207
+ id: preferenceSnapshot.id,
208
+ createdAt: preferenceSnapshot.createdAt,
209
+ descriptorHash: preferenceSnapshot.descriptorHash,
210
+ descriptorCount: preferenceSnapshot.descriptors.length,
211
+ truncated: preferenceSnapshot.truncated,
212
+ }
213
+ : { status: "missing" as const },
214
+ drift: {
215
+ overall: overallDriftStatus(policyStatus, preferenceStatus),
216
+ policy: {
217
+ status: policyStatus,
218
+ snapshotHash: policySnapshotHash,
219
+ currentHash: policyCurrentHash,
220
+ snapshotTargetCount: policySnapshotTargetCount,
221
+ currentTargetCount: policyCurrentTargetCount,
222
+ },
223
+ preferences: {
224
+ status: preferenceStatus,
225
+ snapshotHash: preferenceSnapshotHash,
226
+ currentHash: currentPreferenceHash,
227
+ snapshotDescriptorCount: snapshotPreferenceCount,
228
+ currentDescriptorCount: currentPreferences.length,
229
+ snapshotTruncated: snapshotPreferenceTruncated,
230
+ currentTruncated: governance.currentPreferences.truncated,
231
+ },
232
+ },
233
+ };
234
+ }
235
+
34
236
  function emptyMemoryStatusCounts(): WorkspaceStateMemoryStatusCounts {
35
237
  return Object.fromEntries(
36
238
  KnowledgeMemoryStatus.options.map((status) => [status, 0]),
@@ -227,10 +429,7 @@ export function projectWorkspaceState(
227
429
  generatedAt: input.generatedAt,
228
430
  truth: {
229
431
  current: { source: "read_time_projection", capturedAt: input.generatedAt },
230
- policySnapshot: {
231
- status: "not_captured",
232
- reason: "workspace_instruction_policy_snapshot_not_implemented",
233
- },
432
+ attemptGovernance: attemptGovernanceProjection(input),
234
433
  },
235
434
  policy: policyProjection(input),
236
435
  knowledge: input.knowledge