@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
package/src/task-notes.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TASK_NOTE_LIST_DEFAULT_LIMIT,
|
|
3
3
|
TASK_NOTE_LIST_MAX_LIMIT,
|
|
4
|
+
TASK_NOTE_MAX_LIFETIME_DAYS,
|
|
4
5
|
TaskNote,
|
|
5
6
|
TaskNoteKind,
|
|
6
7
|
TaskNoteListResponse,
|
|
7
8
|
TaskNoteMutationResult,
|
|
8
9
|
TaskNoteReason,
|
|
10
|
+
TaskNoteReplacementResult,
|
|
9
11
|
TaskNoteText,
|
|
10
12
|
type TaskNoteKind as TaskNoteKindType,
|
|
11
13
|
} from "@opengeni/contracts";
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
12
15
|
import { sql } from "drizzle-orm";
|
|
13
16
|
import type { Database } from "./database";
|
|
14
17
|
import { rawRows, withRlsContext } from "./database";
|
|
@@ -36,6 +39,16 @@ export type ArchiveTaskNoteInput = TaskNoteAttemptClaims & {
|
|
|
36
39
|
reason: string;
|
|
37
40
|
};
|
|
38
41
|
|
|
42
|
+
export type ReplaceTaskNoteInput = TaskNoteAttemptClaims & {
|
|
43
|
+
operationId: string;
|
|
44
|
+
replacedNoteId: string;
|
|
45
|
+
expectedReplacedVersion: number;
|
|
46
|
+
replacementKind: TaskNoteKindType;
|
|
47
|
+
replacementText: string;
|
|
48
|
+
replacementExpiresInDays: number;
|
|
49
|
+
reason: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
39
52
|
export type ListTaskNotesInput = TaskNoteAttemptClaims & {
|
|
40
53
|
includeArchived?: boolean;
|
|
41
54
|
limit?: number;
|
|
@@ -58,6 +71,27 @@ type TaskNoteRow = {
|
|
|
58
71
|
replayed?: boolean;
|
|
59
72
|
};
|
|
60
73
|
|
|
74
|
+
type TaskNoteReplacementRow = {
|
|
75
|
+
operation_id: string;
|
|
76
|
+
input_hash: string;
|
|
77
|
+
replaced_note_id: string;
|
|
78
|
+
replaced_note_version: number;
|
|
79
|
+
replacement_note_id: string;
|
|
80
|
+
root_session_id: string;
|
|
81
|
+
replacement_kind: string;
|
|
82
|
+
replacement_text: string;
|
|
83
|
+
replacement_status: string;
|
|
84
|
+
replacement_version: number;
|
|
85
|
+
replacement_expires_at: Date | string;
|
|
86
|
+
replacement_created_at: Date | string;
|
|
87
|
+
replacement_updated_at: Date | string;
|
|
88
|
+
replacement_archived_at: Date | string | null;
|
|
89
|
+
replacement_actor_kind: string;
|
|
90
|
+
replacement_source_session_id: string;
|
|
91
|
+
replacement_source_turn_id: string;
|
|
92
|
+
replayed: boolean;
|
|
93
|
+
};
|
|
94
|
+
|
|
61
95
|
function iso(value: Date | string): string {
|
|
62
96
|
return (value instanceof Date ? value : new Date(value)).toISOString();
|
|
63
97
|
}
|
|
@@ -88,6 +122,20 @@ function assertAttemptClaims(claims: TaskNoteAttemptClaims): void {
|
|
|
88
122
|
}
|
|
89
123
|
}
|
|
90
124
|
|
|
125
|
+
function derivedTaskNoteOperationId(operationId: string, label: "archive" | "create"): string {
|
|
126
|
+
const bytes = Buffer.from(
|
|
127
|
+
createHash("sha256")
|
|
128
|
+
.update(`task-note-replacement:v1:${operationId}:${label}`, "utf8")
|
|
129
|
+
.digest("hex")
|
|
130
|
+
.slice(0, 32),
|
|
131
|
+
"hex",
|
|
132
|
+
);
|
|
133
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x50;
|
|
134
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
135
|
+
const hex = bytes.toString("hex");
|
|
136
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
91
139
|
export async function createTaskNote(db: Database, input: CreateTaskNoteInput) {
|
|
92
140
|
assertAttemptClaims(input);
|
|
93
141
|
TaskNoteKind.parse(input.kind);
|
|
@@ -95,9 +143,9 @@ export async function createTaskNote(db: Database, input: CreateTaskNoteInput) {
|
|
|
95
143
|
if (
|
|
96
144
|
!Number.isSafeInteger(input.expiresInDays) ||
|
|
97
145
|
input.expiresInDays < 1 ||
|
|
98
|
-
input.expiresInDays >
|
|
146
|
+
input.expiresInDays > TASK_NOTE_MAX_LIFETIME_DAYS
|
|
99
147
|
) {
|
|
100
|
-
throw new Error(
|
|
148
|
+
throw new Error(`Task note expiry must be 1-${TASK_NOTE_MAX_LIFETIME_DAYS} whole days`);
|
|
101
149
|
}
|
|
102
150
|
const rows = await withRlsContext(
|
|
103
151
|
db,
|
|
@@ -150,6 +198,80 @@ export async function archiveTaskNote(db: Database, input: ArchiveTaskNoteInput)
|
|
|
150
198
|
return TaskNoteMutationResult.parse({ note: noteFromRow(rows[0]!), replayed: rows[0]!.replayed });
|
|
151
199
|
}
|
|
152
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Atomically archive one exact active note and create its immutable replacement.
|
|
203
|
+
* Exact retries return the original linked receipt; changed input or authority fails.
|
|
204
|
+
*/
|
|
205
|
+
export async function replaceTaskNote(db: Database, input: ReplaceTaskNoteInput) {
|
|
206
|
+
assertAttemptClaims(input);
|
|
207
|
+
TaskNoteKind.parse(input.replacementKind);
|
|
208
|
+
TaskNoteText.parse(input.replacementText);
|
|
209
|
+
TaskNoteReason.parse(input.reason);
|
|
210
|
+
if (
|
|
211
|
+
input.expectedReplacedVersion !== 1 ||
|
|
212
|
+
!Number.isSafeInteger(input.replacementExpiresInDays) ||
|
|
213
|
+
input.replacementExpiresInDays < 1 ||
|
|
214
|
+
input.replacementExpiresInDays > TASK_NOTE_MAX_LIFETIME_DAYS
|
|
215
|
+
) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Task-note replacement requires version 1 and a 1-${TASK_NOTE_MAX_LIFETIME_DAYS} day expiry`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const archiveOperationId = derivedTaskNoteOperationId(input.operationId, "archive");
|
|
221
|
+
const createOperationId = derivedTaskNoteOperationId(input.operationId, "create");
|
|
222
|
+
const rows = await withRlsContext(
|
|
223
|
+
db,
|
|
224
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
225
|
+
async (tx) =>
|
|
226
|
+
await rawRows<TaskNoteReplacementRow>(
|
|
227
|
+
tx,
|
|
228
|
+
sql`SELECT * FROM replace_task_note_for_attempt(
|
|
229
|
+
${input.accountId}::uuid,
|
|
230
|
+
${input.workspaceId}::uuid,
|
|
231
|
+
${input.sessionId}::uuid,
|
|
232
|
+
${input.turnId}::uuid,
|
|
233
|
+
${input.attemptId}::uuid,
|
|
234
|
+
${input.executionGeneration}::integer,
|
|
235
|
+
${input.operationId}::uuid,
|
|
236
|
+
${archiveOperationId}::uuid,
|
|
237
|
+
${createOperationId}::uuid,
|
|
238
|
+
${input.replacedNoteId}::uuid,
|
|
239
|
+
${input.expectedReplacedVersion}::integer,
|
|
240
|
+
${input.replacementKind}::text,
|
|
241
|
+
${input.replacementText}::text,
|
|
242
|
+
${input.replacementExpiresInDays}::integer,
|
|
243
|
+
${input.reason}::text
|
|
244
|
+
)`,
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
const row = rows[0];
|
|
248
|
+
if (!row || rows.length !== 1) throw new Error("Task-note replacement returned no receipt");
|
|
249
|
+
return TaskNoteReplacementResult.parse({
|
|
250
|
+
operationId: row.operation_id,
|
|
251
|
+
inputHash: row.input_hash,
|
|
252
|
+
replaces: {
|
|
253
|
+
noteId: row.replaced_note_id,
|
|
254
|
+
archivedVersion: row.replaced_note_version,
|
|
255
|
+
},
|
|
256
|
+
replacement: noteFromRow({
|
|
257
|
+
note_id: row.replacement_note_id,
|
|
258
|
+
root_session_id: row.root_session_id,
|
|
259
|
+
kind: row.replacement_kind,
|
|
260
|
+
note_text: row.replacement_text,
|
|
261
|
+
status: row.replacement_status,
|
|
262
|
+
version: row.replacement_version,
|
|
263
|
+
expires_at: row.replacement_expires_at,
|
|
264
|
+
created_at: row.replacement_created_at,
|
|
265
|
+
updated_at: row.replacement_updated_at,
|
|
266
|
+
archived_at: row.replacement_archived_at,
|
|
267
|
+
actor_kind: row.replacement_actor_kind,
|
|
268
|
+
source_session_id: row.replacement_source_session_id,
|
|
269
|
+
source_turn_id: row.replacement_source_turn_id,
|
|
270
|
+
}),
|
|
271
|
+
replayed: row.replayed,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
153
275
|
export async function listTaskNotes(db: Database, input: ListTaskNotesInput) {
|
|
154
276
|
assertAttemptClaims(input);
|
|
155
277
|
const limit = input.limit ?? TASK_NOTE_LIST_DEFAULT_LIMIT;
|
package/src/turn-initiator.ts
CHANGED
|
@@ -16,6 +16,8 @@ export const UNATTRIBUTED_LEGACY_INITIATOR: TurnInitiator = {
|
|
|
16
16
|
export type FrozenTurnInitiator = {
|
|
17
17
|
initiator: TurnInitiator;
|
|
18
18
|
context: TurnInitiatorContext;
|
|
19
|
+
/** Exact causal human frozen only after the caller/attempt authority is validated. */
|
|
20
|
+
initiatingHumanSubjectId?: string | null;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
const MAX_AGENT_PROVENANCE_HOPS = 32;
|
|
@@ -100,6 +102,7 @@ export async function frozenInitiatorForCommandActor(
|
|
|
100
102
|
...(actor.subjectLabel ? { label: actor.subjectLabel } : {}),
|
|
101
103
|
},
|
|
102
104
|
context: { ...(actor.context ?? {}) },
|
|
105
|
+
initiatingHumanSubjectId: null,
|
|
103
106
|
};
|
|
104
107
|
}
|
|
105
108
|
if (actor.type !== "agent_attempt") {
|
|
@@ -110,6 +113,7 @@ export async function frozenInitiatorForCommandActor(
|
|
|
110
113
|
...(subjectLabel ? { label: subjectLabel } : {}),
|
|
111
114
|
},
|
|
112
115
|
context: {},
|
|
116
|
+
initiatingHumanSubjectId: actor.subjectId,
|
|
113
117
|
};
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -118,6 +122,7 @@ export async function frozenInitiatorForCommandActor(
|
|
|
118
122
|
initiatorKind: schema.sessionTurns.initiatorKind,
|
|
119
123
|
initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
|
|
120
124
|
initiatorContext: schema.sessionTurns.initiatorContext,
|
|
125
|
+
initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
|
|
121
126
|
})
|
|
122
127
|
.from(schema.sessionTurns)
|
|
123
128
|
.where(
|
|
@@ -151,5 +156,8 @@ export async function frozenInitiatorForCommandActor(
|
|
|
151
156
|
via: clipped,
|
|
152
157
|
...(hops.length > clipped.length ? { viaTruncated: true } : {}),
|
|
153
158
|
},
|
|
159
|
+
initiatingHumanSubjectId:
|
|
160
|
+
turn.initiatingHumanSubjectId ??
|
|
161
|
+
(turn.initiatorKind === "subject" ? turn.initiatorSubjectId : null),
|
|
154
162
|
};
|
|
155
163
|
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PersonalResourceAttachmentSummary,
|
|
3
|
+
type PersonalResourceAttachmentIntent,
|
|
4
|
+
USER_RESOURCE_ACTION_BY_KIND,
|
|
5
|
+
type UserResourceDelegation,
|
|
6
|
+
type UserResourceKind,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
import { sql } from "drizzle-orm";
|
|
9
|
+
import { rawRows, setSubjectRlsContext, withRlsContext, type Database } from "./database";
|
|
10
|
+
import { nestedPostgresSqlState } from "./persistence-errors";
|
|
11
|
+
|
|
12
|
+
export type UserResourceAuthorityGrant = {
|
|
13
|
+
grantId: string;
|
|
14
|
+
targetWorkspaceId: string;
|
|
15
|
+
targetSessionId: string | null;
|
|
16
|
+
action: (typeof USER_RESOURCE_ACTION_BY_KIND)[UserResourceKind];
|
|
17
|
+
mode: "once" | "session" | "always";
|
|
18
|
+
context: "user_private" | "workspace_shared";
|
|
19
|
+
authorityEpoch: number | null;
|
|
20
|
+
generation: number;
|
|
21
|
+
status: "active" | "consumed" | "revoked" | "expired";
|
|
22
|
+
expiresAt: string | null;
|
|
23
|
+
delegation: UserResourceDelegation;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type UserResourceAuthoritySummary = {
|
|
27
|
+
authorityId: string;
|
|
28
|
+
resourceKind: UserResourceKind;
|
|
29
|
+
resourceId: string;
|
|
30
|
+
originWorkspaceId: string | null;
|
|
31
|
+
generation: number;
|
|
32
|
+
status: "active" | "retained" | "revoked";
|
|
33
|
+
grants: UserResourceAuthorityGrant[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type AcceptedTurnPersonalResourceAttachment = {
|
|
37
|
+
summary: PersonalResourceAttachmentSummary;
|
|
38
|
+
replay: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export class PersonalResourceAttachmentAcceptanceError extends Error {
|
|
42
|
+
readonly name = "PersonalResourceAttachmentAcceptanceError";
|
|
43
|
+
constructor(
|
|
44
|
+
readonly kind: "invalid" | "forbidden" | "conflict",
|
|
45
|
+
options?: ErrorOptions,
|
|
46
|
+
) {
|
|
47
|
+
super(
|
|
48
|
+
kind === "invalid"
|
|
49
|
+
? "The personal-resource attachment request is invalid"
|
|
50
|
+
: kind === "forbidden"
|
|
51
|
+
? "The personal-resource attachment is not authorized"
|
|
52
|
+
: "The personal-resource attachment conflicts with accepted work",
|
|
53
|
+
options,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Issue and freeze the locked session's personal Variable Set/Rig/Connected
|
|
60
|
+
* Machine closure on
|
|
61
|
+
* one already-inserted logical turn. The caller owns the surrounding accepted-
|
|
62
|
+
* work transaction; this helper must never be called as a standalone grant
|
|
63
|
+
* mutation.
|
|
64
|
+
*/
|
|
65
|
+
export async function acceptTurnPersonalResourceAttachmentInTransaction(
|
|
66
|
+
db: Database,
|
|
67
|
+
input: {
|
|
68
|
+
accountId: string;
|
|
69
|
+
workspaceId: string;
|
|
70
|
+
sessionId: string;
|
|
71
|
+
turnId: string;
|
|
72
|
+
subjectId: string;
|
|
73
|
+
intent: PersonalResourceAttachmentIntent & { expectedAuthorityEpoch: number };
|
|
74
|
+
},
|
|
75
|
+
): Promise<AcceptedTurnPersonalResourceAttachment> {
|
|
76
|
+
await setSubjectRlsContext(db, input.subjectId);
|
|
77
|
+
let row:
|
|
78
|
+
| {
|
|
79
|
+
grantMode: "once" | "session" | "always";
|
|
80
|
+
grantContext: "user_private" | "workspace_shared";
|
|
81
|
+
resourceCount: number;
|
|
82
|
+
resourceKinds: Array<"variable_set" | "rig" | "connected_machine">;
|
|
83
|
+
sharedOutputWarningVersion: 1;
|
|
84
|
+
replay: boolean;
|
|
85
|
+
}
|
|
86
|
+
| undefined;
|
|
87
|
+
try {
|
|
88
|
+
[row] = await rawRows(
|
|
89
|
+
db,
|
|
90
|
+
sql`
|
|
91
|
+
select grant_mode as "grantMode", grant_context as "grantContext",
|
|
92
|
+
resource_count::int as "resourceCount", resource_kinds as "resourceKinds",
|
|
93
|
+
shared_output_warning_version::int as "sharedOutputWarningVersion", replay
|
|
94
|
+
from accept_turn_personal_resource_attachment(
|
|
95
|
+
${input.accountId}::uuid, ${input.workspaceId}::uuid,
|
|
96
|
+
${input.sessionId}::uuid, ${input.turnId}::uuid, ${input.intent.mode},
|
|
97
|
+
${input.intent.expectedAuthorityEpoch},
|
|
98
|
+
${input.intent.workspaceSharedAcknowledged},
|
|
99
|
+
${input.intent.sharedOutputWarningVersion}
|
|
100
|
+
)
|
|
101
|
+
`,
|
|
102
|
+
);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const sqlState = nestedPostgresSqlState(error);
|
|
105
|
+
if (sqlState === "22023") {
|
|
106
|
+
throw new PersonalResourceAttachmentAcceptanceError("invalid", { cause: error });
|
|
107
|
+
}
|
|
108
|
+
if (sqlState === "42501") {
|
|
109
|
+
throw new PersonalResourceAttachmentAcceptanceError("forbidden", { cause: error });
|
|
110
|
+
}
|
|
111
|
+
if (sqlState === "23505") {
|
|
112
|
+
throw new PersonalResourceAttachmentAcceptanceError("conflict", { cause: error });
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
if (!row) throw new Error("atomic personal-resource attachment was not returned");
|
|
117
|
+
return {
|
|
118
|
+
summary: PersonalResourceAttachmentSummary.parse({
|
|
119
|
+
mode: row.grantMode,
|
|
120
|
+
context: row.grantContext,
|
|
121
|
+
resourceCount: row.resourceCount,
|
|
122
|
+
resourceKinds: row.resourceKinds,
|
|
123
|
+
sharedOutputWarningVersion: row.sharedOutputWarningVersion,
|
|
124
|
+
}),
|
|
125
|
+
replay: row.replay,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
type AuthorityRow = Omit<
|
|
130
|
+
UserResourceAuthorityGrant,
|
|
131
|
+
"grantId" | "targetWorkspaceId" | "delegation"
|
|
132
|
+
> & {
|
|
133
|
+
authorityId: string;
|
|
134
|
+
organizationId: string;
|
|
135
|
+
resourceKind: UserResourceKind;
|
|
136
|
+
resourceId: string;
|
|
137
|
+
originWorkspaceId: string | null;
|
|
138
|
+
authorityGeneration: number;
|
|
139
|
+
authorityStatus: UserResourceAuthoritySummary["status"];
|
|
140
|
+
grantId: string | null;
|
|
141
|
+
targetWorkspaceId: string | null;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
function rfc3339(value: string | null): string | null {
|
|
145
|
+
if (value === null) return null;
|
|
146
|
+
const date = new Date(value);
|
|
147
|
+
if (Number.isNaN(date.getTime())) throw new Error("invalid database timestamptz projection");
|
|
148
|
+
return date.toISOString();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function withOwnerContext<T>(
|
|
152
|
+
db: Database,
|
|
153
|
+
input: { accountId: string; workspaceId: string; subjectId: string },
|
|
154
|
+
fn: (db: Database) => Promise<T>,
|
|
155
|
+
): Promise<T> {
|
|
156
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
157
|
+
await setSubjectRlsContext(scopedDb, input.subjectId);
|
|
158
|
+
return await fn(scopedDb);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function listSelfUserResourceAuthorities(
|
|
163
|
+
db: Database,
|
|
164
|
+
input: {
|
|
165
|
+
accountId: string;
|
|
166
|
+
workspaceId: string;
|
|
167
|
+
subjectId: string;
|
|
168
|
+
resourceKind: UserResourceKind;
|
|
169
|
+
cursor?: string | undefined;
|
|
170
|
+
limit: number;
|
|
171
|
+
},
|
|
172
|
+
): Promise<{ authorities: UserResourceAuthoritySummary[]; nextCursor: string | null }> {
|
|
173
|
+
return await withOwnerContext(db, input, async (scopedDb) => {
|
|
174
|
+
const rows = await rawRows<AuthorityRow>(
|
|
175
|
+
scopedDb,
|
|
176
|
+
sql`
|
|
177
|
+
select authority_id as "authorityId", organization_id as "organizationId",
|
|
178
|
+
resource_kind as "resourceKind", resource_id as "resourceId",
|
|
179
|
+
origin_workspace_id as "originWorkspaceId",
|
|
180
|
+
authority_generation::int as "authorityGeneration",
|
|
181
|
+
authority_status as "authorityStatus", grant_id as "grantId",
|
|
182
|
+
target_workspace_id as "targetWorkspaceId", target_session_id as "targetSessionId",
|
|
183
|
+
action, grant_mode as mode, grant_context as context,
|
|
184
|
+
authority_epoch::int as "authorityEpoch",
|
|
185
|
+
grant_generation::int as generation, grant_status as status,
|
|
186
|
+
expires_at::text as "expiresAt"
|
|
187
|
+
from list_self_user_resource_authorities(
|
|
188
|
+
${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.resourceKind},
|
|
189
|
+
${input.cursor ?? null}::uuid, ${input.limit + 1}
|
|
190
|
+
)
|
|
191
|
+
`,
|
|
192
|
+
);
|
|
193
|
+
const grouped = new Map<string, UserResourceAuthoritySummary>();
|
|
194
|
+
for (const row of rows) {
|
|
195
|
+
const authority = grouped.get(row.authorityId) ?? {
|
|
196
|
+
authorityId: row.authorityId,
|
|
197
|
+
resourceKind: row.resourceKind,
|
|
198
|
+
resourceId: row.resourceId,
|
|
199
|
+
originWorkspaceId: row.originWorkspaceId,
|
|
200
|
+
generation: row.authorityGeneration,
|
|
201
|
+
status: row.authorityStatus,
|
|
202
|
+
grants: [],
|
|
203
|
+
};
|
|
204
|
+
if (row.grantId && row.targetWorkspaceId) {
|
|
205
|
+
authority.grants.push({
|
|
206
|
+
grantId: row.grantId,
|
|
207
|
+
targetWorkspaceId: row.targetWorkspaceId,
|
|
208
|
+
targetSessionId: row.targetSessionId,
|
|
209
|
+
action: row.action,
|
|
210
|
+
mode: row.mode,
|
|
211
|
+
context: row.context,
|
|
212
|
+
authorityEpoch: row.authorityEpoch,
|
|
213
|
+
generation: row.generation,
|
|
214
|
+
status: row.status,
|
|
215
|
+
expiresAt: rfc3339(row.expiresAt),
|
|
216
|
+
delegation: {
|
|
217
|
+
authorityId: row.authorityId,
|
|
218
|
+
grantId: row.grantId,
|
|
219
|
+
organizationId: row.organizationId,
|
|
220
|
+
workspaceId: row.targetWorkspaceId,
|
|
221
|
+
sessionId: row.targetSessionId,
|
|
222
|
+
action: row.action,
|
|
223
|
+
mode: row.mode,
|
|
224
|
+
context: row.context,
|
|
225
|
+
authorityEpoch: row.authorityEpoch,
|
|
226
|
+
authorityGeneration: row.authorityGeneration,
|
|
227
|
+
grantGeneration: row.generation,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
grouped.set(row.authorityId, authority);
|
|
232
|
+
}
|
|
233
|
+
const values = [...grouped.values()];
|
|
234
|
+
const hasMore = values.length > input.limit;
|
|
235
|
+
const authorities = hasMore ? values.slice(0, input.limit) : values;
|
|
236
|
+
return {
|
|
237
|
+
authorities,
|
|
238
|
+
nextCursor: hasMore ? (authorities.at(-1)?.authorityId ?? null) : null,
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export async function issueSelfUserResourceGrant(
|
|
244
|
+
db: Database,
|
|
245
|
+
input: {
|
|
246
|
+
accountId: string;
|
|
247
|
+
workspaceId: string;
|
|
248
|
+
subjectId: string;
|
|
249
|
+
authorityId: string;
|
|
250
|
+
resourceKind: UserResourceKind;
|
|
251
|
+
mode: "session" | "always";
|
|
252
|
+
context: "user_private" | "workspace_shared";
|
|
253
|
+
sessionId?: string | null;
|
|
254
|
+
expectedAuthorityEpoch?: number | null;
|
|
255
|
+
workspaceSharedAcknowledged: boolean;
|
|
256
|
+
},
|
|
257
|
+
): Promise<UserResourceAuthorityGrant> {
|
|
258
|
+
return await withOwnerContext(db, input, async (scopedDb) => {
|
|
259
|
+
const [row] = await rawRows<
|
|
260
|
+
Omit<UserResourceAuthorityGrant, "delegation"> & {
|
|
261
|
+
organizationId: string;
|
|
262
|
+
authorityGeneration: number;
|
|
263
|
+
}
|
|
264
|
+
>(
|
|
265
|
+
scopedDb,
|
|
266
|
+
sql`
|
|
267
|
+
select grant_id as "grantId", organization_id as "organizationId",
|
|
268
|
+
authority_generation::int as "authorityGeneration",
|
|
269
|
+
target_workspace_id as "targetWorkspaceId",
|
|
270
|
+
target_session_id as "targetSessionId", action, grant_mode as mode,
|
|
271
|
+
grant_context as context, authority_epoch::int as "authorityEpoch",
|
|
272
|
+
grant_generation::int as generation,
|
|
273
|
+
grant_status as status, expires_at::text as "expiresAt"
|
|
274
|
+
from issue_self_user_resource_grant(
|
|
275
|
+
${input.accountId}::uuid, ${input.authorityId}::uuid, ${input.workspaceId}::uuid,
|
|
276
|
+
${input.resourceKind}, ${input.mode}, ${input.context},
|
|
277
|
+
${input.sessionId ?? null}::uuid, ${input.expectedAuthorityEpoch ?? null}::integer,
|
|
278
|
+
${input.workspaceSharedAcknowledged}
|
|
279
|
+
)
|
|
280
|
+
`,
|
|
281
|
+
);
|
|
282
|
+
if (!row) throw new Error("user-resource grant was not returned");
|
|
283
|
+
const { organizationId, authorityGeneration, ...grant } = row;
|
|
284
|
+
return {
|
|
285
|
+
...grant,
|
|
286
|
+
expiresAt: rfc3339(grant.expiresAt),
|
|
287
|
+
delegation: {
|
|
288
|
+
authorityId: input.authorityId,
|
|
289
|
+
grantId: grant.grantId,
|
|
290
|
+
organizationId,
|
|
291
|
+
workspaceId: grant.targetWorkspaceId,
|
|
292
|
+
sessionId: grant.targetSessionId,
|
|
293
|
+
action: grant.action,
|
|
294
|
+
mode: grant.mode,
|
|
295
|
+
context: grant.context,
|
|
296
|
+
authorityEpoch: grant.authorityEpoch,
|
|
297
|
+
authorityGeneration,
|
|
298
|
+
grantGeneration: grant.generation,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function revokeSelfUserResourceGrant(
|
|
305
|
+
db: Database,
|
|
306
|
+
input: { accountId: string; workspaceId: string; subjectId: string; grantId: string },
|
|
307
|
+
): Promise<{ grantId: string; generation: number; status: "revoked"; revokedAt: string }> {
|
|
308
|
+
return await withOwnerContext(db, input, async (scopedDb) => {
|
|
309
|
+
const [row] = await rawRows<{
|
|
310
|
+
grantId: string;
|
|
311
|
+
generation: number;
|
|
312
|
+
status: "revoked";
|
|
313
|
+
revokedAt: string;
|
|
314
|
+
}>(
|
|
315
|
+
scopedDb,
|
|
316
|
+
sql`
|
|
317
|
+
select grant_id as "grantId", grant_generation::int as generation,
|
|
318
|
+
grant_status as status, revoked_at::text as "revokedAt"
|
|
319
|
+
from revoke_self_user_resource_grant(
|
|
320
|
+
${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.grantId}::uuid
|
|
321
|
+
)
|
|
322
|
+
`,
|
|
323
|
+
);
|
|
324
|
+
if (!row) throw new Error("user-resource grant was not returned");
|
|
325
|
+
return { ...row, revokedAt: rfc3339(row.revokedAt)! };
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export async function resolveSessionAttemptPersonalResources(
|
|
330
|
+
db: Database,
|
|
331
|
+
input: { accountId: string; workspaceId: string; subjectId: string | null; attemptId: string },
|
|
332
|
+
): Promise<void> {
|
|
333
|
+
await withRlsContext(db, input, async (scopedDb) => {
|
|
334
|
+
if (input.subjectId) await setSubjectRlsContext(scopedDb, input.subjectId);
|
|
335
|
+
await scopedDb.execute(sql`select authorize_session_attempt_personal_resource_reads(
|
|
336
|
+
${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.attemptId}::uuid
|
|
337
|
+
)`);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export type PersonalDocumentAuthority = {
|
|
342
|
+
authorityId: string;
|
|
343
|
+
ownerOrganizationMembershipId: string;
|
|
344
|
+
authorityGeneration: number;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Create the common organization-user authority before inserting a personal
|
|
349
|
+
* Document. Returns null for a configured/local or rolling-legacy subject that
|
|
350
|
+
* has no eligible active organization membership, preserving the existing
|
|
351
|
+
* workspace-anchored personal lane. Call this inside the same database
|
|
352
|
+
* transaction as the Document insert so a failed write cannot leave an orphan
|
|
353
|
+
* authority.
|
|
354
|
+
*/
|
|
355
|
+
export async function createPersonalDocumentAuthority(
|
|
356
|
+
db: Database,
|
|
357
|
+
input: {
|
|
358
|
+
accountId: string;
|
|
359
|
+
workspaceId: string;
|
|
360
|
+
subjectId: string;
|
|
361
|
+
documentId: string;
|
|
362
|
+
},
|
|
363
|
+
): Promise<PersonalDocumentAuthority | null> {
|
|
364
|
+
await setSubjectRlsContext(db, input.subjectId);
|
|
365
|
+
const [row] = await rawRows<PersonalDocumentAuthority>(
|
|
366
|
+
db,
|
|
367
|
+
sql`
|
|
368
|
+
select authority_id as "authorityId",
|
|
369
|
+
owner_organization_membership_id as "ownerOrganizationMembershipId",
|
|
370
|
+
authority_generation::int as "authorityGeneration"
|
|
371
|
+
from create_personal_document_authority(
|
|
372
|
+
${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.documentId}::uuid
|
|
373
|
+
)
|
|
374
|
+
`,
|
|
375
|
+
);
|
|
376
|
+
return row ?? null;
|
|
377
|
+
}
|