@opengeni/db 0.36.1 → 3.1.0-canary.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/dist/attached-browser-devices.d.ts +7 -0
- package/dist/automations.d.ts +154 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-integrations.d.ts +13 -0
- package/dist/child-lifecycle-notices.d.ts +68 -0
- package/dist/{chunk-GYO7ED2Q.js → chunk-EULAN2NP.js} +2 -2
- package/dist/{chunk-VTPRIXKG.js → chunk-NJVZPWXY.js} +3 -3
- package/dist/{chunk-HSOWSYOX.js → chunk-NMUBV7Z5.js} +19 -11
- package/dist/chunk-NMUBV7Z5.js.map +1 -0
- package/dist/chunk-OF5OBB76.js +4457 -0
- package/dist/chunk-OF5OBB76.js.map +1 -0
- package/dist/{chunk-5KTIEDX7.js → chunk-PR2WMLOQ.js} +7714 -4443
- package/dist/chunk-PR2WMLOQ.js.map +1 -0
- package/dist/{chunk-Z7VYMNPZ.js → chunk-SCPUJS53.js} +41 -4
- package/dist/chunk-SCPUJS53.js.map +1 -0
- package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
- package/dist/chunk-SIBYBA2D.js.map +1 -0
- package/dist/chunk-TIKF3KTI.js +383 -0
- package/dist/chunk-TIKF3KTI.js.map +1 -0
- package/dist/chunk-ZZ5LJEFX.js +516 -0
- package/dist/chunk-ZZ5LJEFX.js.map +1 -0
- package/dist/codemode-operations.d.ts +1 -6
- package/dist/company-brain-context-selection-schema.d.ts +705 -0
- package/dist/company-brain-context-selection.d.ts +94 -0
- package/dist/company-brain-governed-writes.d.ts +24 -0
- package/dist/company-brain.d.ts +55 -0
- package/dist/company-profile-agent-admin.d.ts +15 -0
- package/dist/company-profile-schema.d.ts +619 -2
- package/dist/company-profile.d.ts +9 -4
- package/dist/connection-authority.d.ts +95 -0
- package/dist/connection-scopes.d.ts +8 -0
- package/dist/connection-tenancy-backfill.d.ts +41 -0
- package/dist/connection-token-resolver.d.ts +30 -1
- package/dist/database.d.ts +19 -0
- package/dist/editable-artifact-durable-export.d.ts +10 -0
- package/dist/editable-artifact-durable-export.js +18 -3
- package/dist/editable-artifact-durable-export.js.map +1 -1
- package/dist/editable-artifacts.d.ts +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation-schema.d.ts +984 -0
- package/dist/governed-learning-activation.d.ts +98 -0
- package/dist/governed-learning-evaluator-schema.d.ts +633 -0
- package/dist/governed-learning-evaluator.d.ts +28 -0
- package/dist/index.d.ts +1633 -87
- package/dist/index.js +22004 -8109
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +18 -0
- package/dist/integration-bindings.d.ts +1 -0
- package/dist/integration-facets.d.ts +30 -0
- package/dist/interaction-placement-loss.d.ts +22 -0
- package/dist/knowledge-source-sync-schema.d.ts +730 -0
- package/dist/knowledge-source-sync.d.ts +60 -0
- package/dist/managed-human-provisioning.d.ts +25 -0
- package/dist/memory-domain.d.ts +3 -3
- package/dist/migrate.d.ts +11 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +43 -5
- package/dist/organization-membership-backfill.d.ts +174 -0
- package/dist/organization-membership-lifecycle.d.ts +162 -0
- package/dist/pr-review.d.ts +107 -0
- package/dist/preference-registry-schema.d.ts +2 -0
- package/dist/preference-registry.d.ts +2 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +23 -9
- package/dist/schema.d.ts +24925 -13697
- package/dist/schema.js +103 -1
- package/dist/scoped-knowledge-schema.d.ts +52 -1
- package/dist/session-background-commands.d.ts +125 -0
- package/dist/session-background-commands.js +34 -0
- package/dist/session-background-commands.js.map +1 -0
- package/dist/session-control.d.ts +156 -3
- package/dist/session-goal-pacing.d.ts +68 -0
- package/dist/session-queue-commands.d.ts +42 -5
- package/dist/session-realtime-ledger.d.ts +3 -0
- package/dist/session-tenancy.d.ts +71 -1
- package/dist/session-tenancy.js +25 -3
- package/dist/session-tool-call-settlement.d.ts +9 -4
- package/dist/slack-routing-personal-workspace.d.ts +23 -0
- package/dist/slack-routing.d.ts +153 -0
- package/dist/slack-task-policy-schema.d.ts +34 -0
- package/dist/slack-task-policy.d.ts +8 -0
- package/dist/slack-user-link-access.d.ts +1 -0
- package/dist/task-notes-schema.d.ts +594 -0
- package/dist/task-notes.d.ts +39 -0
- package/dist/turn-initiator.d.ts +2 -0
- package/dist/user-resource-authority.d.ts +109 -0
- package/dist/video-generation.js +3 -3
- package/dist/workspace-authority.d.ts +81 -0
- package/dist/workspace-instruction-policies-schema.d.ts +279 -0
- package/dist/workspace-instruction-policies.d.ts +62 -0
- package/dist/workspace-learning-policy.d.ts +1 -0
- package/dist/workspace-membership-permissions.d.ts +2 -0
- package/drizzle/0240_enrollment_connection_authority.sql +39 -0
- package/drizzle/0240_model_context_user_messages.sql +204 -0
- package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
- package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
- package/drizzle/0243_google_drive_object_acl_authority.sql +554 -0
- package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
- package/drizzle/0245_model_context_contribution_facts.sql +88 -0
- package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
- package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
- package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
- package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
- package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
- package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
- package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
- package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
- package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
- package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
- package/drizzle/0256_connection_authority_delegation.sql +703 -0
- package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
- package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
- package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
- package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
- package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
- package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
- package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
- package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
- package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
- package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
- package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
- package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
- package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
- package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
- package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
- package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
- package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
- package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
- package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
- package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
- package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
- package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
- package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
- package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
- package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
- package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
- package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
- package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
- package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
- package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
- package/drizzle/0289_session_composer_policy_authority.sql +597 -0
- package/drizzle/0290_organization_membership_backfill.sql +268 -0
- package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
- package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
- package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
- package/drizzle/0294_preference_activation_authority.sql +237 -0
- package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
- package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
- package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
- package/drizzle/0298_organization_tenancy_parity.sql +609 -0
- package/drizzle/0299_organization_membership_lock_order.sql +291 -0
- package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
- package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
- package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
- package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
- package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
- package/drizzle/0305_personal_resource_grant_management.sql +584 -0
- package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
- package/drizzle/0307_session_attention_state.sql +51 -0
- package/drizzle/0308_session_archives.sql +35 -0
- package/drizzle/0309_channel_project_pins.sql +6 -0
- package/drizzle/0310_channel_project_order.sql +27 -0
- package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
- package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
- package/drizzle/0313_private_child_session_authority.sql +365 -0
- package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
- package/drizzle/0315_personal_github_repository_selection.sql +624 -0
- package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
- package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
- package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
- package/drizzle/0319_event_triggered_automations.sql +254 -0
- package/drizzle/0320_pr_review_pack.sql +113 -0
- package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
- package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
- package/drizzle/0323_organization_private_session_enablement.sql +489 -0
- package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
- package/drizzle/0325_child_lifecycle_notices.sql +44 -0
- package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
- package/drizzle/0327_slack_first_task_hint.sql +29 -0
- package/drizzle/0328_session_background_commands.sql +590 -0
- package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
- package/drizzle/0330_api_key_descriptions.sql +20 -0
- package/drizzle/0331_managed_organization_creation.sql +163 -0
- package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
- package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
- package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
- package/drizzle/0335_slack_workspace_routing.sql +460 -0
- package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
- package/drizzle/0337_slack_routed_action_handles.sql +189 -0
- package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
- package/drizzle/0339_document_authority_reclassification.sql +1153 -0
- package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
- package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
- package/package.json +10 -6
- package/src/attached-browser-devices.ts +233 -5
- package/src/automations.ts +911 -0
- package/src/browser-sessions.ts +10 -0
- package/src/capability-integrations.ts +175 -1
- package/src/child-lifecycle-notices.ts +241 -0
- package/src/codemode-operations.ts +1 -35
- package/src/company-brain-context-selection-schema.ts +100 -0
- package/src/company-brain-context-selection.ts +359 -0
- package/src/company-brain-governed-writes.ts +1170 -0
- package/src/company-brain.ts +309 -0
- package/src/company-profile-agent-admin.ts +225 -0
- package/src/company-profile-schema.ts +121 -1
- package/src/company-profile.ts +21 -0
- package/src/computer-sessions.ts +10 -1
- package/src/connection-authority.ts +290 -0
- package/src/connection-scopes.ts +20 -0
- package/src/connection-tenancy-backfill.ts +81 -0
- package/src/connection-token-resolver.ts +263 -31
- package/src/database.ts +75 -2
- package/src/editable-artifact-durable-export.ts +27 -0
- package/src/editable-artifacts-schema.ts +10 -5
- package/src/editable-artifacts.ts +21 -11
- package/src/governed-learning-activation-schema.ts +121 -0
- package/src/governed-learning-activation.ts +588 -0
- package/src/governed-learning-evaluator-schema.ts +72 -0
- package/src/governed-learning-evaluator.ts +217 -0
- package/src/index.ts +12001 -1699
- package/src/insights.ts +107 -0
- package/src/integration-bindings.ts +88 -6
- package/src/integration-facets.ts +287 -84
- package/src/interaction-placement-loss.ts +234 -0
- package/src/knowledge-source-sync-schema.ts +83 -0
- package/src/knowledge-source-sync.ts +530 -0
- package/src/managed-human-provisioning.ts +116 -0
- package/src/memory-domain.ts +27 -32
- package/src/migrate.ts +77 -1
- package/src/new-session-drafts.ts +229 -35
- package/src/organization-membership-backfill.ts +735 -0
- package/src/organization-membership-lifecycle.ts +985 -0
- package/src/pr-review.ts +890 -0
- package/src/preference-registry.ts +31 -1
- package/src/provision-roles.ts +931 -24
- package/src/runtime-posture-cli.ts +2 -0
- package/src/runtime-posture.ts +1315 -52
- package/src/schema.ts +3336 -438
- package/src/scoped-knowledge-schema.ts +16 -8
- package/src/scoped-knowledge.ts +1 -1
- package/src/session-background-commands.ts +759 -0
- package/src/session-control.ts +640 -74
- package/src/session-goal-pacing.ts +126 -0
- package/src/session-queue-commands.ts +450 -98
- package/src/session-realtime-context.ts +19 -3
- package/src/session-realtime-ledger.ts +75 -12
- package/src/session-realtime-mirror.ts +1 -1
- package/src/session-tenancy.ts +403 -49
- package/src/session-tool-call-settlement.ts +38 -29
- package/src/slack-routing-personal-workspace.ts +67 -0
- package/src/slack-routing.ts +469 -0
- package/src/slack-task-policy-schema.ts +5 -0
- package/src/slack-task-policy.ts +18 -0
- package/src/slack-user-link-access.ts +17 -1
- package/src/task-notes-schema.ts +74 -0
- package/src/task-notes.ts +124 -2
- package/src/turn-initiator.ts +8 -0
- package/src/user-resource-authority.ts +377 -0
- package/src/workspace-authority.ts +160 -0
- package/src/workspace-instruction-policies-schema.ts +53 -1
- package/src/workspace-instruction-policies.ts +528 -77
- package/src/workspace-learning-policy.ts +21 -5
- package/src/workspace-membership-permissions.ts +9 -0
- package/dist/chunk-5KTIEDX7.js.map +0 -1
- package/dist/chunk-FPVAD4CO.js +0 -117
- package/dist/chunk-FPVAD4CO.js.map +0 -1
- package/dist/chunk-HSOWSYOX.js.map +0 -1
- package/dist/chunk-SK7YYHBI.js.map +0 -1
- package/dist/chunk-VKLB2ZGI.js +0 -2389
- package/dist/chunk-VKLB2ZGI.js.map +0 -1
- package/dist/chunk-Z7VYMNPZ.js.map +0 -1
- /package/dist/{chunk-GYO7ED2Q.js.map → chunk-EULAN2NP.js.map} +0 -0
- /package/dist/{chunk-VTPRIXKG.js.map → chunk-NJVZPWXY.js.map} +0 -0
|
@@ -9,9 +9,11 @@ import type {
|
|
|
9
9
|
WorkspaceInstructionPolicyActivationEvent,
|
|
10
10
|
WorkspaceInstructionPolicyActivationResponse,
|
|
11
11
|
WorkspaceInstructionPolicyActivationType,
|
|
12
|
+
WorkspaceInstructionPolicyDeactivationEvent,
|
|
12
13
|
WorkspaceInstructionPolicyDiffResponse,
|
|
13
14
|
WorkspaceInstructionPolicyDraftProvenanceSource,
|
|
14
15
|
WorkspaceInstructionPolicyHead,
|
|
16
|
+
WorkspaceInstructionPolicyInactiveHead,
|
|
15
17
|
WorkspaceInstructionPolicyKind,
|
|
16
18
|
WorkspaceInstructionPolicyListQuery,
|
|
17
19
|
WorkspaceInstructionPolicyListResponse,
|
|
@@ -24,9 +26,14 @@ import type {
|
|
|
24
26
|
WorkspaceInstructionPolicySnapshotEntry,
|
|
25
27
|
WorkspaceInstructionPolicyTarget,
|
|
26
28
|
} from "@opengeni/contracts";
|
|
27
|
-
import { and, asc, desc, eq, inArray, isNull, lt, or, sql, type SQL } from "drizzle-orm";
|
|
29
|
+
import { and, asc, desc, eq, inArray, isNull, lt, notExists, or, sql, type SQL } from "drizzle-orm";
|
|
28
30
|
import type { Database } from "./database";
|
|
29
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
withRlsContext,
|
|
33
|
+
withSessionRlsActorContext,
|
|
34
|
+
withWorkspaceRls,
|
|
35
|
+
withWorkspaceSubjectRls,
|
|
36
|
+
} from "./database";
|
|
30
37
|
import { nestedPostgresSqlState } from "./persistence-errors";
|
|
31
38
|
import * as schema from "./schema";
|
|
32
39
|
|
|
@@ -87,6 +94,19 @@ type OnboardingProposalInput = OnboardingProposalRequestInput & {
|
|
|
87
94
|
requestFingerprint: string;
|
|
88
95
|
};
|
|
89
96
|
|
|
97
|
+
export type WorkspaceInstructionPolicyKnowledgeProposalInput = WorkspaceInstructionPolicyTarget & {
|
|
98
|
+
operationId: string;
|
|
99
|
+
accountId: string;
|
|
100
|
+
workspaceId: string;
|
|
101
|
+
content: string;
|
|
102
|
+
knowledgeProposalId: string;
|
|
103
|
+
knowledgeProposalContentHash: string;
|
|
104
|
+
confidenceBps: number;
|
|
105
|
+
expectedCurrentRevisionId: string | null;
|
|
106
|
+
expectedActivationVersion: number;
|
|
107
|
+
createdBySubjectId: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
90
110
|
export class WorkspaceInstructionPolicyConflictError extends Error {
|
|
91
111
|
readonly name = "WorkspaceInstructionPolicyConflictError";
|
|
92
112
|
readonly code = "WORKSPACE_INSTRUCTION_POLICY_CONFLICT";
|
|
@@ -141,6 +161,16 @@ export class WorkspaceInstructionPolicyOnboardingProposalContentError extends Er
|
|
|
141
161
|
}
|
|
142
162
|
}
|
|
143
163
|
|
|
164
|
+
/**
|
|
165
|
+
* The exact diagnostic `activate_human_confirmed_learning_decision` raises when
|
|
166
|
+
* a proposal's compare-and-set baseline no longer matches the live head. It is
|
|
167
|
+
* matched by message because the SQLSTATE (40001) is shared with other
|
|
168
|
+
* conflicts. Kept here so the SQL and the code that keys recovery off it cannot
|
|
169
|
+
* drift apart silently - the migration test pins the same string.
|
|
170
|
+
*/
|
|
171
|
+
export const INSTRUCTION_POLICY_STALE_BASELINE_DIAGNOSTIC =
|
|
172
|
+
"instruction-policy proposal baseline is stale";
|
|
173
|
+
|
|
144
174
|
export class WorkspaceInstructionPolicyOnboardingProposalStaleError extends Error {
|
|
145
175
|
readonly name = "WorkspaceInstructionPolicyOnboardingProposalStaleError";
|
|
146
176
|
readonly code = "WORKSPACE_INSTRUCTION_POLICY_ONBOARDING_PROPOSAL_STALE";
|
|
@@ -268,6 +298,14 @@ function activeRevisionRequestFingerprint(input: ActiveRevisionInput): string {
|
|
|
268
298
|
]);
|
|
269
299
|
}
|
|
270
300
|
|
|
301
|
+
// The activation baseline is deliberately NOT part of either fingerprint. Its
|
|
302
|
+
// job is compare-and-set staleness detection at write time, not the identity of
|
|
303
|
+
// the request: two calls that ask for the same rule on the same target are the
|
|
304
|
+
// same operation even if the head moved between them. Hashing a live-read value
|
|
305
|
+
// into the identity made an ordinary turn-recovery replay of the same
|
|
306
|
+
// operationId fail as an operation-reuse conflict once a confirm advanced the
|
|
307
|
+
// head. Staleness is still enforced - by the compare-and-set below and by the
|
|
308
|
+
// activation function - it is just no longer conflated with request identity.
|
|
271
309
|
function onboardingProposalRequestFingerprint(input: OnboardingProposalRequestInput): string {
|
|
272
310
|
return operationRequestFingerprint("create_onboarding_proposal", [
|
|
273
311
|
["accountId", true, input.accountId],
|
|
@@ -279,8 +317,21 @@ function onboardingProposalRequestFingerprint(input: OnboardingProposalRequestIn
|
|
|
279
317
|
["sourceId", true, input.sourceId],
|
|
280
318
|
["sourceVersion", true, input.sourceVersion],
|
|
281
319
|
["confidenceBps", true, input.confidenceBps],
|
|
282
|
-
["
|
|
283
|
-
|
|
320
|
+
["createdBySubjectId", true, input.createdBySubjectId],
|
|
321
|
+
]);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function knowledgeProposalRequestFingerprint(input: OnboardingProposalRequestInput): string {
|
|
325
|
+
return operationRequestFingerprint("create_knowledge_proposal", [
|
|
326
|
+
["accountId", true, input.accountId],
|
|
327
|
+
["workspaceId", true, input.workspaceId],
|
|
328
|
+
["kind", true, input.kind],
|
|
329
|
+
["scope", true, input.scope],
|
|
330
|
+
["roleKey", true, input.roleKey],
|
|
331
|
+
["content", true, input.content],
|
|
332
|
+
["knowledgeProposalId", true, input.sourceId],
|
|
333
|
+
["knowledgeProposalContentHash", true, input.sourceVersion],
|
|
334
|
+
["confidenceBps", true, input.confidenceBps],
|
|
284
335
|
["createdBySubjectId", true, input.createdBySubjectId],
|
|
285
336
|
]);
|
|
286
337
|
}
|
|
@@ -420,6 +471,7 @@ export async function getWorkspaceStateAcceptedAttemptGovernance(
|
|
|
420
471
|
type RevisionRow = typeof schema.workspaceInstructionPolicyRevisions.$inferSelect;
|
|
421
472
|
type HeadRow = typeof schema.workspaceInstructionPolicyHeads.$inferSelect;
|
|
422
473
|
type EventRow = typeof schema.workspaceInstructionPolicyActivationEvents.$inferSelect;
|
|
474
|
+
type DeactivationEventRow = typeof schema.workspaceInstructionPolicyDeactivationEvents.$inferSelect;
|
|
423
475
|
type OnboardingProposalRow =
|
|
424
476
|
typeof schema.workspaceInstructionPolicyOnboardingProposals.$inferSelect;
|
|
425
477
|
|
|
@@ -459,6 +511,17 @@ function headFromRow(row: HeadRow): WorkspaceInstructionPolicyHead {
|
|
|
459
511
|
};
|
|
460
512
|
}
|
|
461
513
|
|
|
514
|
+
function inactiveHeadFromRow(row: DeactivationEventRow): WorkspaceInstructionPolicyInactiveHead {
|
|
515
|
+
return {
|
|
516
|
+
workspaceId: row.workspaceId,
|
|
517
|
+
kind: row.kind as WorkspaceInstructionPolicyKind,
|
|
518
|
+
scope: row.scope as WorkspaceInstructionPolicyScope,
|
|
519
|
+
roleKey: row.roleKey,
|
|
520
|
+
activationVersion: row.activationVersion,
|
|
521
|
+
deactivatedAt: iso(row.createdAt),
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
462
525
|
function eventFromRow(row: EventRow): WorkspaceInstructionPolicyActivationEvent {
|
|
463
526
|
return {
|
|
464
527
|
id: row.id,
|
|
@@ -489,6 +552,30 @@ function eventFromRow(row: EventRow): WorkspaceInstructionPolicyActivationEvent
|
|
|
489
552
|
};
|
|
490
553
|
}
|
|
491
554
|
|
|
555
|
+
function deactivationEventFromRow(
|
|
556
|
+
row: DeactivationEventRow,
|
|
557
|
+
): WorkspaceInstructionPolicyDeactivationEvent {
|
|
558
|
+
return {
|
|
559
|
+
id: row.id,
|
|
560
|
+
operationId: row.operationId,
|
|
561
|
+
accountId: row.accountId,
|
|
562
|
+
workspaceId: row.workspaceId,
|
|
563
|
+
kind: row.kind as WorkspaceInstructionPolicyKind,
|
|
564
|
+
scope: row.scope as WorkspaceInstructionPolicyScope,
|
|
565
|
+
roleKey: row.roleKey,
|
|
566
|
+
type: "automatic_deactivate",
|
|
567
|
+
activationVersion: row.activationVersion,
|
|
568
|
+
oldRevision: {
|
|
569
|
+
id: row.oldRevisionId,
|
|
570
|
+
revision: row.oldRevision,
|
|
571
|
+
contentHash: row.oldContentHash,
|
|
572
|
+
},
|
|
573
|
+
actorSubjectId: row.actorSubjectId,
|
|
574
|
+
reason: row.reason,
|
|
575
|
+
createdAt: iso(row.createdAt),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
492
579
|
function onboardingProposalFromRow(
|
|
493
580
|
row: OnboardingProposalRow,
|
|
494
581
|
draftRow: RevisionRow,
|
|
@@ -569,6 +656,20 @@ function eventTargetConditions(
|
|
|
569
656
|
];
|
|
570
657
|
}
|
|
571
658
|
|
|
659
|
+
function deactivationTargetConditions(
|
|
660
|
+
workspaceId: string,
|
|
661
|
+
target: WorkspaceInstructionPolicyTarget,
|
|
662
|
+
): SQL[] {
|
|
663
|
+
return [
|
|
664
|
+
eq(schema.workspaceInstructionPolicyDeactivationEvents.workspaceId, workspaceId),
|
|
665
|
+
eq(schema.workspaceInstructionPolicyDeactivationEvents.kind, target.kind),
|
|
666
|
+
eq(schema.workspaceInstructionPolicyDeactivationEvents.scope, target.scope),
|
|
667
|
+
target.roleKey === null
|
|
668
|
+
? isNull(schema.workspaceInstructionPolicyDeactivationEvents.roleKey)
|
|
669
|
+
: eq(schema.workspaceInstructionPolicyDeactivationEvents.roleKey, target.roleKey),
|
|
670
|
+
];
|
|
671
|
+
}
|
|
672
|
+
|
|
572
673
|
async function lockWorkspace(
|
|
573
674
|
db: Database,
|
|
574
675
|
input: { accountId: string; workspaceId: string },
|
|
@@ -656,6 +757,24 @@ async function getEventByOperationInTransaction(
|
|
|
656
757
|
return row ?? null;
|
|
657
758
|
}
|
|
658
759
|
|
|
760
|
+
async function hasDeactivationOperationInTransaction(
|
|
761
|
+
db: Database,
|
|
762
|
+
workspaceId: string,
|
|
763
|
+
operationId: string,
|
|
764
|
+
): Promise<boolean> {
|
|
765
|
+
const [row] = await db
|
|
766
|
+
.select({ id: schema.workspaceInstructionPolicyDeactivationEvents.id })
|
|
767
|
+
.from(schema.workspaceInstructionPolicyDeactivationEvents)
|
|
768
|
+
.where(
|
|
769
|
+
and(
|
|
770
|
+
eq(schema.workspaceInstructionPolicyDeactivationEvents.workspaceId, workspaceId),
|
|
771
|
+
eq(schema.workspaceInstructionPolicyDeactivationEvents.operationId, operationId),
|
|
772
|
+
),
|
|
773
|
+
)
|
|
774
|
+
.limit(1);
|
|
775
|
+
return row !== undefined;
|
|
776
|
+
}
|
|
777
|
+
|
|
659
778
|
async function getOnboardingProposalByOperationInTransaction(
|
|
660
779
|
db: Database,
|
|
661
780
|
workspaceId: string,
|
|
@@ -696,6 +815,11 @@ async function getOnboardingProposalBySourceVersionInTransaction(
|
|
|
696
815
|
eq(schema.workspaceInstructionPolicyOnboardingProposals.sourceVersion, input.sourceVersion),
|
|
697
816
|
),
|
|
698
817
|
)
|
|
818
|
+
.orderBy(
|
|
819
|
+
desc(schema.workspaceInstructionPolicyOnboardingProposals.baselineActivationVersion),
|
|
820
|
+
desc(schema.workspaceInstructionPolicyOnboardingProposals.createdAt),
|
|
821
|
+
desc(schema.workspaceInstructionPolicyOnboardingProposals.id),
|
|
822
|
+
)
|
|
699
823
|
.limit(1);
|
|
700
824
|
return row ?? null;
|
|
701
825
|
}
|
|
@@ -725,6 +849,23 @@ async function getHeadInTransaction(
|
|
|
725
849
|
return row ?? null;
|
|
726
850
|
}
|
|
727
851
|
|
|
852
|
+
async function getLatestDeactivationInTransaction(
|
|
853
|
+
db: Database,
|
|
854
|
+
workspaceId: string,
|
|
855
|
+
target: WorkspaceInstructionPolicyTarget,
|
|
856
|
+
): Promise<DeactivationEventRow | null> {
|
|
857
|
+
const [row] = await db
|
|
858
|
+
.select()
|
|
859
|
+
.from(schema.workspaceInstructionPolicyDeactivationEvents)
|
|
860
|
+
.where(and(...deactivationTargetConditions(workspaceId, target)))
|
|
861
|
+
.orderBy(
|
|
862
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.activationVersion),
|
|
863
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.id),
|
|
864
|
+
)
|
|
865
|
+
.limit(1);
|
|
866
|
+
return row ?? null;
|
|
867
|
+
}
|
|
868
|
+
|
|
728
869
|
function sameTarget(
|
|
729
870
|
left: Pick<WorkspaceInstructionPolicyRevision, "kind" | "scope" | "roleKey">,
|
|
730
871
|
right: WorkspaceInstructionPolicyTarget,
|
|
@@ -749,7 +890,10 @@ async function createDraftInTransaction(
|
|
|
749
890
|
db: Database,
|
|
750
891
|
input: DraftInput,
|
|
751
892
|
): Promise<WorkspaceInstructionPolicyRevision> {
|
|
752
|
-
if (
|
|
893
|
+
if (
|
|
894
|
+
(await getEventByOperationInTransaction(db, input.workspaceId, input.operationId)) ||
|
|
895
|
+
(await hasDeactivationOperationInTransaction(db, input.workspaceId, input.operationId))
|
|
896
|
+
) {
|
|
753
897
|
throw new WorkspaceInstructionPolicyOperationReuseError();
|
|
754
898
|
}
|
|
755
899
|
const existing = await getRevisionByOperationInTransaction(
|
|
@@ -844,7 +988,16 @@ export async function importLegacyWorkspaceInstructionPolicyDraft(
|
|
|
844
988
|
async (scopedDb) => {
|
|
845
989
|
const workspace = await lockWorkspace(scopedDb, input);
|
|
846
990
|
if (
|
|
847
|
-
await getEventByOperationInTransaction(
|
|
991
|
+
(await getEventByOperationInTransaction(
|
|
992
|
+
scopedDb,
|
|
993
|
+
input.workspaceId,
|
|
994
|
+
normalized.operationId,
|
|
995
|
+
)) ||
|
|
996
|
+
(await hasDeactivationOperationInTransaction(
|
|
997
|
+
scopedDb,
|
|
998
|
+
input.workspaceId,
|
|
999
|
+
normalized.operationId,
|
|
1000
|
+
))
|
|
848
1001
|
) {
|
|
849
1002
|
throw new WorkspaceInstructionPolicyOperationReuseError();
|
|
850
1003
|
}
|
|
@@ -893,30 +1046,12 @@ export async function importLegacyWorkspaceInstructionPolicyDraft(
|
|
|
893
1046
|
);
|
|
894
1047
|
}
|
|
895
1048
|
|
|
896
|
-
|
|
1049
|
+
async function createWorkspaceInstructionPolicySourceProposal(
|
|
897
1050
|
db: Database,
|
|
898
|
-
input:
|
|
1051
|
+
input: OnboardingProposalInput,
|
|
1052
|
+
draftProvenanceSource: WorkspaceInstructionPolicyDraftProvenanceSource,
|
|
1053
|
+
draftProvenanceSourceId: (proposalId: string) => string,
|
|
899
1054
|
): Promise<WorkspaceInstructionPolicyOnboardingProposal> {
|
|
900
|
-
if (input.content.trim().length === 0) {
|
|
901
|
-
throw new WorkspaceInstructionPolicyOnboardingProposalContentError(
|
|
902
|
-
"WORKSPACE_INSTRUCTION_POLICY_ONBOARDING_PROPOSAL_EMPTY",
|
|
903
|
-
);
|
|
904
|
-
}
|
|
905
|
-
if (input.content.length > WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS) {
|
|
906
|
-
throw new WorkspaceInstructionPolicyOnboardingProposalContentError(
|
|
907
|
-
"WORKSPACE_INSTRUCTION_POLICY_ONBOARDING_PROPOSAL_OVERSIZED",
|
|
908
|
-
);
|
|
909
|
-
}
|
|
910
|
-
const request: OnboardingProposalRequestInput = {
|
|
911
|
-
...input,
|
|
912
|
-
operationId: input.operationId ?? randomUUID(),
|
|
913
|
-
sourceId: input.sourceId.trim(),
|
|
914
|
-
sourceVersion: input.sourceVersion.trim(),
|
|
915
|
-
};
|
|
916
|
-
const normalized: OnboardingProposalInput = {
|
|
917
|
-
...request,
|
|
918
|
-
requestFingerprint: onboardingProposalRequestFingerprint(request),
|
|
919
|
-
};
|
|
920
1055
|
return await withRlsContext(
|
|
921
1056
|
db,
|
|
922
1057
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
@@ -925,48 +1060,58 @@ export async function createWorkspaceInstructionPolicyOnboardingProposal(
|
|
|
925
1060
|
const existing = await getOnboardingProposalByOperationInTransaction(
|
|
926
1061
|
scopedDb,
|
|
927
1062
|
input.workspaceId,
|
|
928
|
-
|
|
1063
|
+
input.operationId,
|
|
929
1064
|
);
|
|
930
1065
|
if (existing) {
|
|
931
|
-
if (existing.requestFingerprint !==
|
|
1066
|
+
if (existing.requestFingerprint !== input.requestFingerprint) {
|
|
932
1067
|
throw new WorkspaceInstructionPolicyOperationReuseError();
|
|
933
1068
|
}
|
|
934
1069
|
return await resolveOnboardingProposalInTransaction(scopedDb, existing);
|
|
935
1070
|
}
|
|
936
1071
|
if (
|
|
937
|
-
(await getEventByOperationInTransaction(
|
|
1072
|
+
(await getEventByOperationInTransaction(scopedDb, input.workspaceId, input.operationId)) ||
|
|
1073
|
+
(await hasDeactivationOperationInTransaction(
|
|
938
1074
|
scopedDb,
|
|
939
1075
|
input.workspaceId,
|
|
940
|
-
|
|
1076
|
+
input.operationId,
|
|
941
1077
|
)) ||
|
|
942
|
-
(await getRevisionByOperationInTransaction(
|
|
943
|
-
scopedDb,
|
|
944
|
-
input.workspaceId,
|
|
945
|
-
normalized.operationId,
|
|
946
|
-
))
|
|
1078
|
+
(await getRevisionByOperationInTransaction(scopedDb, input.workspaceId, input.operationId))
|
|
947
1079
|
) {
|
|
948
1080
|
throw new WorkspaceInstructionPolicyOperationReuseError();
|
|
949
1081
|
}
|
|
950
1082
|
|
|
951
1083
|
const target: WorkspaceInstructionPolicyTarget = {
|
|
952
|
-
kind:
|
|
953
|
-
scope:
|
|
954
|
-
roleKey:
|
|
1084
|
+
kind: input.kind,
|
|
1085
|
+
scope: input.scope,
|
|
1086
|
+
roleKey: input.roleKey,
|
|
955
1087
|
};
|
|
956
1088
|
const currentRow = await getHeadInTransaction(scopedDb, input.workspaceId, target);
|
|
957
1089
|
const currentHead = currentRow ? headFromRow(currentRow) : null;
|
|
1090
|
+
const inactiveBoundary = currentRow
|
|
1091
|
+
? null
|
|
1092
|
+
: await getLatestDeactivationInTransaction(scopedDb, input.workspaceId, target);
|
|
1093
|
+
const currentActivationVersion =
|
|
1094
|
+
currentRow?.activationVersion ?? inactiveBoundary?.activationVersion ?? 0;
|
|
958
1095
|
if (
|
|
959
|
-
(currentHead?.revisionId ?? null) !==
|
|
960
|
-
|
|
1096
|
+
(currentHead?.revisionId ?? null) !== input.expectedCurrentRevisionId ||
|
|
1097
|
+
currentActivationVersion !== input.expectedActivationVersion
|
|
961
1098
|
) {
|
|
962
1099
|
throw new WorkspaceInstructionPolicyOnboardingProposalStaleError(currentHead);
|
|
963
1100
|
}
|
|
964
1101
|
|
|
1102
|
+
// A second proposal for the same knowledge proposal is legitimate when it
|
|
1103
|
+
// is a rebaselined successor - the confirm path adds one when the head
|
|
1104
|
+
// moved after the human already answered. Only a duplicate against the
|
|
1105
|
+
// same baseline is a conflict.
|
|
965
1106
|
const sourceConflict = await getOnboardingProposalBySourceVersionInTransaction(
|
|
966
1107
|
scopedDb,
|
|
967
|
-
|
|
1108
|
+
input,
|
|
968
1109
|
);
|
|
969
|
-
if (
|
|
1110
|
+
if (
|
|
1111
|
+
sourceConflict &&
|
|
1112
|
+
sourceConflict.baselineActivationVersion === currentActivationVersion &&
|
|
1113
|
+
(sourceConflict.baselineRevisionId ?? null) === (currentHead?.revisionId ?? null)
|
|
1114
|
+
) {
|
|
970
1115
|
throw new WorkspaceInstructionPolicyOnboardingProposalConflictError(
|
|
971
1116
|
sourceConflict.id,
|
|
972
1117
|
sourceConflict.draftRevisionId,
|
|
@@ -975,68 +1120,74 @@ export async function createWorkspaceInstructionPolicyOnboardingProposal(
|
|
|
975
1120
|
|
|
976
1121
|
const proposalId = randomUUID();
|
|
977
1122
|
const draft = await createDraftInTransaction(scopedDb, {
|
|
978
|
-
operationId:
|
|
979
|
-
requestFingerprint:
|
|
980
|
-
accountId:
|
|
981
|
-
workspaceId:
|
|
982
|
-
kind:
|
|
983
|
-
scope:
|
|
984
|
-
roleKey:
|
|
985
|
-
content:
|
|
986
|
-
provenanceSource:
|
|
987
|
-
provenanceSourceId: proposalId,
|
|
1123
|
+
operationId: input.operationId,
|
|
1124
|
+
requestFingerprint: input.requestFingerprint,
|
|
1125
|
+
accountId: input.accountId,
|
|
1126
|
+
workspaceId: input.workspaceId,
|
|
1127
|
+
kind: input.kind,
|
|
1128
|
+
scope: input.scope,
|
|
1129
|
+
roleKey: input.roleKey,
|
|
1130
|
+
content: input.content,
|
|
1131
|
+
provenanceSource: draftProvenanceSource,
|
|
1132
|
+
provenanceSourceId: draftProvenanceSourceId(proposalId),
|
|
988
1133
|
supersedesRevisionId: currentHead?.revisionId ?? null,
|
|
989
|
-
createdBySubjectId:
|
|
1134
|
+
createdBySubjectId: input.createdBySubjectId,
|
|
990
1135
|
});
|
|
991
1136
|
const createdAt = new Date();
|
|
992
1137
|
const [created] = await scopedDb
|
|
993
1138
|
.insert(schema.workspaceInstructionPolicyOnboardingProposals)
|
|
994
1139
|
.values({
|
|
995
1140
|
id: proposalId,
|
|
996
|
-
operationId:
|
|
997
|
-
requestFingerprint:
|
|
998
|
-
accountId:
|
|
999
|
-
workspaceId:
|
|
1000
|
-
kind:
|
|
1001
|
-
scope:
|
|
1002
|
-
roleKey:
|
|
1003
|
-
sourceId:
|
|
1004
|
-
sourceVersion:
|
|
1005
|
-
confidenceBps:
|
|
1141
|
+
operationId: input.operationId,
|
|
1142
|
+
requestFingerprint: input.requestFingerprint,
|
|
1143
|
+
accountId: input.accountId,
|
|
1144
|
+
workspaceId: input.workspaceId,
|
|
1145
|
+
kind: input.kind,
|
|
1146
|
+
scope: input.scope,
|
|
1147
|
+
roleKey: input.roleKey,
|
|
1148
|
+
sourceId: input.sourceId,
|
|
1149
|
+
sourceVersion: input.sourceVersion,
|
|
1150
|
+
confidenceBps: input.confidenceBps,
|
|
1006
1151
|
baselineRevisionId: currentHead?.revisionId ?? null,
|
|
1007
1152
|
baselineRevision: currentHead?.revision ?? null,
|
|
1008
1153
|
baselineContentHash: currentHead?.contentHash ?? null,
|
|
1009
|
-
baselineActivationVersion:
|
|
1010
|
-
|
|
1154
|
+
baselineActivationVersion: currentActivationVersion,
|
|
1155
|
+
// The head's activated_at may carry microseconds (the governed
|
|
1156
|
+
// learning SQL controller writes clock_timestamp()), and the draft
|
|
1157
|
+
// trigger compares it exactly. A JS Date truncates to milliseconds,
|
|
1158
|
+
// so copy the stored value in SQL instead of round-tripping it.
|
|
1159
|
+
baselineActivatedAt: currentRow
|
|
1160
|
+
? sql`(select ${schema.workspaceInstructionPolicyHeads.activatedAt} from ${schema.workspaceInstructionPolicyHeads} where ${schema.workspaceInstructionPolicyHeads.id} = ${currentRow.id})`
|
|
1161
|
+
: null,
|
|
1011
1162
|
draftRevisionId: draft.id,
|
|
1012
1163
|
draftRevision: draft.revision,
|
|
1013
1164
|
draftContentHash: draft.contentHash,
|
|
1014
1165
|
status: "proposed",
|
|
1015
|
-
createdBySubjectId:
|
|
1166
|
+
createdBySubjectId: input.createdBySubjectId,
|
|
1016
1167
|
createdAt,
|
|
1017
1168
|
})
|
|
1018
1169
|
.onConflictDoNothing()
|
|
1019
1170
|
.returning();
|
|
1020
1171
|
if (created) {
|
|
1021
1172
|
const draftRow = await getRevisionInTransaction(scopedDb, input.workspaceId, draft.id);
|
|
1022
|
-
if (!draftRow) throw new Error("
|
|
1173
|
+
if (!draftRow) throw new Error("Instruction-policy proposal draft was not recorded");
|
|
1023
1174
|
return onboardingProposalFromRow(created, draftRow);
|
|
1024
1175
|
}
|
|
1025
1176
|
|
|
1026
1177
|
const concurrentOperation = await getOnboardingProposalByOperationInTransaction(
|
|
1027
1178
|
scopedDb,
|
|
1028
1179
|
input.workspaceId,
|
|
1029
|
-
|
|
1180
|
+
input.operationId,
|
|
1030
1181
|
);
|
|
1031
1182
|
if (concurrentOperation) {
|
|
1032
|
-
if (concurrentOperation.requestFingerprint !==
|
|
1183
|
+
if (concurrentOperation.requestFingerprint !== input.requestFingerprint) {
|
|
1033
1184
|
throw new WorkspaceInstructionPolicyOperationReuseError();
|
|
1034
1185
|
}
|
|
1035
1186
|
return await resolveOnboardingProposalInTransaction(scopedDb, concurrentOperation);
|
|
1036
1187
|
}
|
|
1037
1188
|
const concurrentSource = await getOnboardingProposalBySourceVersionInTransaction(
|
|
1038
1189
|
scopedDb,
|
|
1039
|
-
|
|
1190
|
+
input,
|
|
1040
1191
|
);
|
|
1041
1192
|
if (concurrentSource) {
|
|
1042
1193
|
throw new WorkspaceInstructionPolicyOnboardingProposalConflictError(
|
|
@@ -1044,8 +1195,171 @@ export async function createWorkspaceInstructionPolicyOnboardingProposal(
|
|
|
1044
1195
|
concurrentSource.draftRevisionId,
|
|
1045
1196
|
);
|
|
1046
1197
|
}
|
|
1047
|
-
throw new Error("Workspace instruction-policy
|
|
1198
|
+
throw new Error("Workspace instruction-policy proposal was not created");
|
|
1199
|
+
},
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
export async function createWorkspaceInstructionPolicyOnboardingProposal(
|
|
1204
|
+
db: Database,
|
|
1205
|
+
input: Omit<OnboardingProposalRequestInput, "operationId"> & { operationId?: string },
|
|
1206
|
+
): Promise<WorkspaceInstructionPolicyOnboardingProposal> {
|
|
1207
|
+
if (input.content.trim().length === 0) {
|
|
1208
|
+
throw new WorkspaceInstructionPolicyOnboardingProposalContentError(
|
|
1209
|
+
"WORKSPACE_INSTRUCTION_POLICY_ONBOARDING_PROPOSAL_EMPTY",
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
if (input.content.length > WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS) {
|
|
1213
|
+
throw new WorkspaceInstructionPolicyOnboardingProposalContentError(
|
|
1214
|
+
"WORKSPACE_INSTRUCTION_POLICY_ONBOARDING_PROPOSAL_OVERSIZED",
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
const request: OnboardingProposalRequestInput = {
|
|
1218
|
+
...input,
|
|
1219
|
+
operationId: input.operationId ?? randomUUID(),
|
|
1220
|
+
sourceId: input.sourceId.trim(),
|
|
1221
|
+
sourceVersion: input.sourceVersion.trim(),
|
|
1222
|
+
};
|
|
1223
|
+
return await createWorkspaceInstructionPolicySourceProposal(
|
|
1224
|
+
db,
|
|
1225
|
+
{ ...request, requestFingerprint: onboardingProposalRequestFingerprint(request) },
|
|
1226
|
+
"onboarding",
|
|
1227
|
+
(proposalId) => proposalId,
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Materialize an immutable Knowledge change proposal as an inactive instruction
|
|
1233
|
+
* draft. Human activation remains exclusively in the existing lifecycle API.
|
|
1234
|
+
*/
|
|
1235
|
+
export async function createWorkspaceInstructionPolicyKnowledgeProposal(
|
|
1236
|
+
db: Database,
|
|
1237
|
+
input: WorkspaceInstructionPolicyKnowledgeProposalInput,
|
|
1238
|
+
): Promise<WorkspaceInstructionPolicyOnboardingProposal> {
|
|
1239
|
+
if (input.content.trim().length === 0) {
|
|
1240
|
+
throw new WorkspaceInstructionPolicyInvalidOperationError(
|
|
1241
|
+
"A Knowledge-backed instruction proposal must contain non-blank content",
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
if (input.content.length > WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS) {
|
|
1245
|
+
throw new WorkspaceInstructionPolicyInvalidOperationError(
|
|
1246
|
+
`A Knowledge-backed instruction proposal must not exceed ${WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS} characters`,
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
if (!/^[0-9a-f]{64}$/.test(input.knowledgeProposalContentHash)) {
|
|
1250
|
+
throw new WorkspaceInstructionPolicyInvalidOperationError(
|
|
1251
|
+
"Knowledge proposal content hash must be lowercase SHA-256",
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
const request: OnboardingProposalRequestInput = {
|
|
1255
|
+
operationId: input.operationId,
|
|
1256
|
+
accountId: input.accountId,
|
|
1257
|
+
workspaceId: input.workspaceId,
|
|
1258
|
+
kind: input.kind,
|
|
1259
|
+
scope: input.scope,
|
|
1260
|
+
roleKey: input.roleKey,
|
|
1261
|
+
content: input.content,
|
|
1262
|
+
sourceId: input.knowledgeProposalId,
|
|
1263
|
+
sourceVersion: input.knowledgeProposalContentHash,
|
|
1264
|
+
confidenceBps: input.confidenceBps,
|
|
1265
|
+
expectedCurrentRevisionId: input.expectedCurrentRevisionId,
|
|
1266
|
+
expectedActivationVersion: input.expectedActivationVersion,
|
|
1267
|
+
createdBySubjectId: input.createdBySubjectId,
|
|
1268
|
+
};
|
|
1269
|
+
return await createWorkspaceInstructionPolicySourceProposal(
|
|
1270
|
+
db,
|
|
1271
|
+
{ ...request, requestFingerprint: knowledgeProposalRequestFingerprint(request) },
|
|
1272
|
+
"knowledge_proposal",
|
|
1273
|
+
() => input.knowledgeProposalId,
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Add a successor instruction-policy proposal for a knowledge proposal whose
|
|
1279
|
+
* existing proposal was left behind by a moved head.
|
|
1280
|
+
*
|
|
1281
|
+
* This exists for one case: the human already answered "save", and only then
|
|
1282
|
+
* did the head move. Re-asking them would be the alternative. The successor
|
|
1283
|
+
* reuses the exact immutable source facts of the stranded proposal - same
|
|
1284
|
+
* knowledge proposal id, same content hash, same target, same author - so the
|
|
1285
|
+
* human's confirmation, which is bound to the knowledge proposal id, still
|
|
1286
|
+
* describes precisely what gets activated. Only the compare-and-set baseline
|
|
1287
|
+
* and the draft's place in the revision chain move forward.
|
|
1288
|
+
*
|
|
1289
|
+
* Returns the existing proposal unchanged when it is already current, so a
|
|
1290
|
+
* retry of the same confirmation is idempotent.
|
|
1291
|
+
*/
|
|
1292
|
+
export async function rebaselineWorkspaceInstructionPolicyKnowledgeProposal(
|
|
1293
|
+
db: Database,
|
|
1294
|
+
input: {
|
|
1295
|
+
accountId: string;
|
|
1296
|
+
workspaceId: string;
|
|
1297
|
+
knowledgeProposalId: string;
|
|
1298
|
+
operationId: string;
|
|
1299
|
+
initiatingHumanSubjectId: string;
|
|
1300
|
+
},
|
|
1301
|
+
): Promise<WorkspaceInstructionPolicyOnboardingProposal> {
|
|
1302
|
+
const stranded = await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
|
|
1303
|
+
const [row] = await scoped
|
|
1304
|
+
.select()
|
|
1305
|
+
.from(schema.workspaceInstructionPolicyOnboardingProposals)
|
|
1306
|
+
.where(
|
|
1307
|
+
and(
|
|
1308
|
+
eq(schema.workspaceInstructionPolicyOnboardingProposals.workspaceId, input.workspaceId),
|
|
1309
|
+
eq(
|
|
1310
|
+
schema.workspaceInstructionPolicyOnboardingProposals.sourceId,
|
|
1311
|
+
input.knowledgeProposalId,
|
|
1312
|
+
),
|
|
1313
|
+
eq(schema.workspaceInstructionPolicyOnboardingProposals.status, "proposed"),
|
|
1314
|
+
),
|
|
1315
|
+
)
|
|
1316
|
+
.orderBy(
|
|
1317
|
+
desc(schema.workspaceInstructionPolicyOnboardingProposals.baselineActivationVersion),
|
|
1318
|
+
desc(schema.workspaceInstructionPolicyOnboardingProposals.createdAt),
|
|
1319
|
+
desc(schema.workspaceInstructionPolicyOnboardingProposals.id),
|
|
1320
|
+
)
|
|
1321
|
+
.limit(1);
|
|
1322
|
+
if (!row) return null;
|
|
1323
|
+
const draft = await getRevisionInTransaction(scoped, input.workspaceId, row.draftRevisionId);
|
|
1324
|
+
return draft ? { row, draft } : null;
|
|
1325
|
+
});
|
|
1326
|
+
if (!stranded) {
|
|
1327
|
+
throw new WorkspaceInstructionPolicyInvalidOperationError(
|
|
1328
|
+
"No inactive instruction-policy proposal exists for this knowledge proposal",
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
const target: WorkspaceInstructionPolicyTarget = {
|
|
1332
|
+
kind: stranded.row.kind as WorkspaceInstructionPolicyKind,
|
|
1333
|
+
scope: stranded.row.scope as WorkspaceInstructionPolicyScope,
|
|
1334
|
+
roleKey: stranded.row.roleKey,
|
|
1335
|
+
};
|
|
1336
|
+
const baseline = await getWorkspaceInstructionPolicyBaseline(db, {
|
|
1337
|
+
workspaceId: input.workspaceId,
|
|
1338
|
+
target,
|
|
1339
|
+
});
|
|
1340
|
+
// The draft-validation trigger binds a knowledge-proposal draft to the
|
|
1341
|
+
// initiating human carried in the transaction, exactly as the original write
|
|
1342
|
+
// did through the governed-write authority seam. The successor must be
|
|
1343
|
+
// created under that same frozen human, not the ambient caller.
|
|
1344
|
+
return await withSessionRlsActorContext(
|
|
1345
|
+
{
|
|
1346
|
+
subjectId: input.initiatingHumanSubjectId,
|
|
1347
|
+
initiatingHumanSubjectId: input.initiatingHumanSubjectId,
|
|
1048
1348
|
},
|
|
1349
|
+
async () =>
|
|
1350
|
+
await createWorkspaceInstructionPolicyKnowledgeProposal(db, {
|
|
1351
|
+
operationId: input.operationId,
|
|
1352
|
+
accountId: input.accountId,
|
|
1353
|
+
workspaceId: input.workspaceId,
|
|
1354
|
+
...target,
|
|
1355
|
+
content: stranded.draft.content,
|
|
1356
|
+
knowledgeProposalId: stranded.row.sourceId,
|
|
1357
|
+
knowledgeProposalContentHash: stranded.row.sourceVersion,
|
|
1358
|
+
confidenceBps: stranded.row.confidenceBps,
|
|
1359
|
+
expectedCurrentRevisionId: baseline.expectedCurrentRevisionId,
|
|
1360
|
+
expectedActivationVersion: baseline.expectedActivationVersion,
|
|
1361
|
+
createdBySubjectId: stranded.row.createdBySubjectId,
|
|
1362
|
+
}),
|
|
1049
1363
|
);
|
|
1050
1364
|
}
|
|
1051
1365
|
|
|
@@ -1119,6 +1433,29 @@ function listFilterConditions(
|
|
|
1119
1433
|
return conditions;
|
|
1120
1434
|
}
|
|
1121
1435
|
|
|
1436
|
+
function inactiveHeadFilterConditions(
|
|
1437
|
+
workspaceId: string,
|
|
1438
|
+
query: WorkspaceInstructionPolicyListQuery,
|
|
1439
|
+
): SQL[] {
|
|
1440
|
+
const conditions: SQL[] = [
|
|
1441
|
+
eq(schema.workspaceInstructionPolicyDeactivationEvents.workspaceId, workspaceId),
|
|
1442
|
+
];
|
|
1443
|
+
if (query.kind !== undefined) {
|
|
1444
|
+
conditions.push(eq(schema.workspaceInstructionPolicyDeactivationEvents.kind, query.kind));
|
|
1445
|
+
}
|
|
1446
|
+
if (query.scope !== undefined) {
|
|
1447
|
+
conditions.push(eq(schema.workspaceInstructionPolicyDeactivationEvents.scope, query.scope));
|
|
1448
|
+
}
|
|
1449
|
+
if (query.roleKey !== undefined) {
|
|
1450
|
+
conditions.push(
|
|
1451
|
+
query.roleKey === null
|
|
1452
|
+
? isNull(schema.workspaceInstructionPolicyDeactivationEvents.roleKey)
|
|
1453
|
+
: eq(schema.workspaceInstructionPolicyDeactivationEvents.roleKey, query.roleKey),
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
return conditions;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1122
1459
|
export async function listWorkspaceInstructionPolicyRevisions(
|
|
1123
1460
|
db: Database,
|
|
1124
1461
|
workspaceId: string,
|
|
@@ -1133,7 +1470,7 @@ export async function listWorkspaceInstructionPolicyRevisions(
|
|
|
1133
1470
|
.limit(query.limit + 1);
|
|
1134
1471
|
const hasMore = rows.length > query.limit;
|
|
1135
1472
|
const page = rows.slice(0, query.limit);
|
|
1136
|
-
const [heads, events] = await Promise.all([
|
|
1473
|
+
const [heads, inactiveHeadRows, events, deactivationEvents] = await Promise.all([
|
|
1137
1474
|
scopedDb
|
|
1138
1475
|
.select()
|
|
1139
1476
|
.from(schema.workspaceInstructionPolicyHeads)
|
|
@@ -1143,20 +1480,82 @@ export async function listWorkspaceInstructionPolicyRevisions(
|
|
|
1143
1480
|
asc(schema.workspaceInstructionPolicyHeads.scope),
|
|
1144
1481
|
asc(schema.workspaceInstructionPolicyHeads.roleKey),
|
|
1145
1482
|
),
|
|
1483
|
+
scopedDb
|
|
1484
|
+
.selectDistinctOn([
|
|
1485
|
+
schema.workspaceInstructionPolicyDeactivationEvents.kind,
|
|
1486
|
+
schema.workspaceInstructionPolicyDeactivationEvents.scope,
|
|
1487
|
+
sql`coalesce(${schema.workspaceInstructionPolicyDeactivationEvents.roleKey}, '')`,
|
|
1488
|
+
])
|
|
1489
|
+
.from(schema.workspaceInstructionPolicyDeactivationEvents)
|
|
1490
|
+
.where(
|
|
1491
|
+
and(
|
|
1492
|
+
...inactiveHeadFilterConditions(workspaceId, query),
|
|
1493
|
+
notExists(
|
|
1494
|
+
scopedDb
|
|
1495
|
+
.select({ id: schema.workspaceInstructionPolicyHeads.id })
|
|
1496
|
+
.from(schema.workspaceInstructionPolicyHeads)
|
|
1497
|
+
.where(
|
|
1498
|
+
and(
|
|
1499
|
+
eq(
|
|
1500
|
+
schema.workspaceInstructionPolicyHeads.workspaceId,
|
|
1501
|
+
schema.workspaceInstructionPolicyDeactivationEvents.workspaceId,
|
|
1502
|
+
),
|
|
1503
|
+
eq(
|
|
1504
|
+
schema.workspaceInstructionPolicyHeads.kind,
|
|
1505
|
+
schema.workspaceInstructionPolicyDeactivationEvents.kind,
|
|
1506
|
+
),
|
|
1507
|
+
eq(
|
|
1508
|
+
schema.workspaceInstructionPolicyHeads.scope,
|
|
1509
|
+
schema.workspaceInstructionPolicyDeactivationEvents.scope,
|
|
1510
|
+
),
|
|
1511
|
+
sql`${schema.workspaceInstructionPolicyHeads.roleKey} IS NOT DISTINCT FROM ${schema.workspaceInstructionPolicyDeactivationEvents.roleKey}`,
|
|
1512
|
+
),
|
|
1513
|
+
),
|
|
1514
|
+
),
|
|
1515
|
+
),
|
|
1516
|
+
)
|
|
1517
|
+
.orderBy(
|
|
1518
|
+
asc(schema.workspaceInstructionPolicyDeactivationEvents.kind),
|
|
1519
|
+
asc(schema.workspaceInstructionPolicyDeactivationEvents.scope),
|
|
1520
|
+
sql`coalesce(${schema.workspaceInstructionPolicyDeactivationEvents.roleKey}, '')`,
|
|
1521
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.activationVersion),
|
|
1522
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.id),
|
|
1523
|
+
)
|
|
1524
|
+
.limit(query.limit + 1),
|
|
1146
1525
|
scopedDb
|
|
1147
1526
|
.select()
|
|
1148
1527
|
.from(schema.workspaceInstructionPolicyActivationEvents)
|
|
1149
|
-
.where(
|
|
1528
|
+
.where(
|
|
1529
|
+
and(
|
|
1530
|
+
eq(schema.workspaceInstructionPolicyActivationEvents.workspaceId, workspaceId),
|
|
1531
|
+
inArray(schema.workspaceInstructionPolicyActivationEvents.type, [
|
|
1532
|
+
"activate",
|
|
1533
|
+
"rollback",
|
|
1534
|
+
]),
|
|
1535
|
+
),
|
|
1536
|
+
)
|
|
1150
1537
|
.orderBy(
|
|
1151
1538
|
desc(schema.workspaceInstructionPolicyActivationEvents.createdAt),
|
|
1152
1539
|
desc(schema.workspaceInstructionPolicyActivationEvents.id),
|
|
1153
1540
|
)
|
|
1154
1541
|
.limit(query.limit),
|
|
1542
|
+
scopedDb
|
|
1543
|
+
.select()
|
|
1544
|
+
.from(schema.workspaceInstructionPolicyDeactivationEvents)
|
|
1545
|
+
.where(eq(schema.workspaceInstructionPolicyDeactivationEvents.workspaceId, workspaceId))
|
|
1546
|
+
.orderBy(
|
|
1547
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.createdAt),
|
|
1548
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.id),
|
|
1549
|
+
)
|
|
1550
|
+
.limit(query.limit),
|
|
1155
1551
|
]);
|
|
1156
1552
|
return {
|
|
1157
1553
|
revisions: page.map(revisionFromRow),
|
|
1158
1554
|
activeHeads: heads.map(headFromRow),
|
|
1555
|
+
inactiveHeads: inactiveHeadRows.slice(0, query.limit).map(inactiveHeadFromRow),
|
|
1556
|
+
inactiveHeadsTruncated: inactiveHeadRows.length > query.limit,
|
|
1159
1557
|
activationEvents: events.map(eventFromRow),
|
|
1558
|
+
deactivationEvents: deactivationEvents.map(deactivationEventFromRow),
|
|
1160
1559
|
nextAfterRevision: hasMore ? page.at(-1)!.revision : null,
|
|
1161
1560
|
};
|
|
1162
1561
|
});
|
|
@@ -1271,6 +1670,16 @@ async function changeActiveRevision(
|
|
|
1271
1670
|
input.workspaceId,
|
|
1272
1671
|
input.operationId,
|
|
1273
1672
|
);
|
|
1673
|
+
if (
|
|
1674
|
+
!existingEvent &&
|
|
1675
|
+
(await hasDeactivationOperationInTransaction(
|
|
1676
|
+
scopedDb,
|
|
1677
|
+
input.workspaceId,
|
|
1678
|
+
input.operationId,
|
|
1679
|
+
))
|
|
1680
|
+
) {
|
|
1681
|
+
throw new WorkspaceInstructionPolicyOperationReuseError();
|
|
1682
|
+
}
|
|
1274
1683
|
if (existingEvent) {
|
|
1275
1684
|
const legacyRequestMatches =
|
|
1276
1685
|
hasOwnField(input, "expectedCurrentRevisionId") &&
|
|
@@ -1303,10 +1712,15 @@ async function changeActiveRevision(
|
|
|
1303
1712
|
};
|
|
1304
1713
|
const currentRow = await getHeadInTransaction(scopedDb, input.workspaceId, targetIdentity);
|
|
1305
1714
|
const currentHead = currentRow ? headFromRow(currentRow) : null;
|
|
1715
|
+
const inactiveBoundary = currentRow
|
|
1716
|
+
? null
|
|
1717
|
+
: await getLatestDeactivationInTransaction(scopedDb, input.workspaceId, targetIdentity);
|
|
1718
|
+
const currentActivationVersion =
|
|
1719
|
+
currentRow?.activationVersion ?? inactiveBoundary?.activationVersion ?? 0;
|
|
1306
1720
|
if (
|
|
1307
1721
|
(currentHead?.revisionId ?? null) !== input.expectedCurrentRevisionId ||
|
|
1308
1722
|
(input.expectedActivationVersion !== undefined &&
|
|
1309
|
-
|
|
1723
|
+
currentActivationVersion !== input.expectedActivationVersion)
|
|
1310
1724
|
) {
|
|
1311
1725
|
throw new WorkspaceInstructionPolicyConflictError(currentHead);
|
|
1312
1726
|
}
|
|
@@ -1337,7 +1751,7 @@ async function changeActiveRevision(
|
|
|
1337
1751
|
);
|
|
1338
1752
|
}
|
|
1339
1753
|
}
|
|
1340
|
-
const activationVersion =
|
|
1754
|
+
const activationVersion = currentActivationVersion + 1;
|
|
1341
1755
|
const createdAt = new Date();
|
|
1342
1756
|
const [eventRow] = await scopedDb
|
|
1343
1757
|
.insert(schema.workspaceInstructionPolicyActivationEvents)
|
|
@@ -1545,3 +1959,40 @@ function snapshotEntryMatchesRevision(
|
|
|
1545
1959
|
revision.roleKey === entry.roleKey
|
|
1546
1960
|
);
|
|
1547
1961
|
}
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* Current activation baseline for one instruction-policy target: the active
|
|
1965
|
+
* head's revision (or null) and the CAS activation version that a new draft
|
|
1966
|
+
* proposal must expect. A deactivated-to-null target keeps its monotonic
|
|
1967
|
+
* boundary, so the version is the max of the head and latest deactivation.
|
|
1968
|
+
* Read-only under workspace RLS; used by `remember` to bind a user-directed
|
|
1969
|
+
* rule to the exact current baseline instead of assuming an empty workspace.
|
|
1970
|
+
*/
|
|
1971
|
+
export async function getWorkspaceInstructionPolicyBaseline(
|
|
1972
|
+
db: Database,
|
|
1973
|
+
input: { workspaceId: string; target: WorkspaceInstructionPolicyTarget },
|
|
1974
|
+
): Promise<{ expectedCurrentRevisionId: string | null; expectedActivationVersion: number }> {
|
|
1975
|
+
return await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
|
|
1976
|
+
const [head] = await scoped
|
|
1977
|
+
.select()
|
|
1978
|
+
.from(schema.workspaceInstructionPolicyHeads)
|
|
1979
|
+
.where(and(...headTargetConditions(input.workspaceId, input.target)))
|
|
1980
|
+
.limit(1);
|
|
1981
|
+
const [deactivation] = await scoped
|
|
1982
|
+
.select()
|
|
1983
|
+
.from(schema.workspaceInstructionPolicyDeactivationEvents)
|
|
1984
|
+
.where(and(...deactivationTargetConditions(input.workspaceId, input.target)))
|
|
1985
|
+
.orderBy(
|
|
1986
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.activationVersion),
|
|
1987
|
+
desc(schema.workspaceInstructionPolicyDeactivationEvents.id),
|
|
1988
|
+
)
|
|
1989
|
+
.limit(1);
|
|
1990
|
+
return {
|
|
1991
|
+
expectedCurrentRevisionId: head?.revisionId ?? null,
|
|
1992
|
+
expectedActivationVersion: Math.max(
|
|
1993
|
+
Number(head?.activationVersion ?? 0),
|
|
1994
|
+
Number(deactivation?.activationVersion ?? 0),
|
|
1995
|
+
),
|
|
1996
|
+
};
|
|
1997
|
+
});
|
|
1998
|
+
}
|