@opengeni/db 1.0.1 → 3.2.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-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
- package/dist/chunk-2EH4WRHL.js.map +1 -0
- package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
- package/dist/chunk-6HMM2R5T.js +390 -0
- package/dist/chunk-6HMM2R5T.js.map +1 -0
- package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
- package/dist/chunk-AZHW7R5V.js.map +1 -0
- package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
- package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
- package/dist/chunk-KRLPEMLH.js.map +1 -0
- package/dist/chunk-NAB6FKFN.js +516 -0
- package/dist/chunk-NAB6FKFN.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-UZNXG44K.js +4508 -0
- package/dist/chunk-UZNXG44K.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 +85 -0
- package/dist/connection-token-resolver.d.ts +30 -1
- package/dist/database.d.ts +23 -4
- package/dist/editable-artifact-durable-export.js +2 -2
- 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 +1606 -81
- package/dist/index.js +21898 -8147
- 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/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 +29 -8
- package/dist/schema.d.ts +24914 -13703
- package/dist/schema.js +99 -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 +43 -4
- package/dist/session-realtime-ledger.d.ts +2 -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 +268 -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 +28 -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/0241_atomic_personal_resource_delegation.sql +1000 -0
- package/drizzle/0241_enrollment_agent_runtime.sql +70 -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/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
- package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
- package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
- package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
- package/drizzle/0346_document_migration_audit_surface.sql +454 -0
- package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -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 +167 -0
- package/src/connection-token-resolver.ts +263 -31
- package/src/database.ts +87 -2
- 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 +11676 -1550
- 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/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 +995 -0
- package/src/pr-review.ts +890 -0
- package/src/preference-registry.ts +31 -1
- package/src/provision-roles.ts +905 -6
- package/src/runtime-posture-cli.ts +2 -0
- package/src/runtime-posture.ts +1317 -72
- package/src/schema.ts +3316 -428
- 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 +441 -85
- package/src/session-realtime-context.ts +4 -6
- package/src/session-realtime-ledger.ts +28 -14
- package/src/session-realtime-mirror.ts +1 -1
- package/src/session-tenancy.ts +410 -49
- package/src/session-tool-call-settlement.ts +38 -29
- package/src/slack-routing-personal-workspace.ts +67 -0
- package/src/slack-routing.ts +867 -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 +138 -11
- 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-4SR4HKKA.js.map +0 -1
- package/dist/chunk-6ZYKCRFO.js.map +0 -1
- package/dist/chunk-NXAFLCQX.js +0 -2468
- package/dist/chunk-NXAFLCQX.js.map +0 -1
- package/dist/chunk-SK7YYHBI.js.map +0 -1
- package/dist/chunk-WD5ROSLL.js +0 -117
- package/dist/chunk-WD5ROSLL.js.map +0 -1
- package/dist/chunk-ZWREYEWA.js.map +0 -1
- /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
- /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.js.map} +0 -0
|
@@ -114,6 +114,11 @@ export const slackSharedTaskOrigins = pgTable(
|
|
|
114
114
|
sourceThreadTs: text("source_thread_ts").notNull(),
|
|
115
115
|
initiatingSlackUserId: text("initiating_slack_user_id").notNull(),
|
|
116
116
|
policyRevisionId: uuid("policy_revision_id").notNull(),
|
|
117
|
+
// The Slack task policy is a home fact and the interaction is a routed one,
|
|
118
|
+
// so the frozen revision carries its own tenancy pair. Null on rows written
|
|
119
|
+
// before Slack workspace routing, where the two were always equal.
|
|
120
|
+
policyAccountId: uuid("policy_account_id"),
|
|
121
|
+
policyWorkspaceId: uuid("policy_workspace_id"),
|
|
117
122
|
policyHash: text("policy_hash").notNull(),
|
|
118
123
|
policyActivationVersion: bigint("policy_activation_version", { mode: "number" }).notNull(),
|
|
119
124
|
publicationMode: text("publication_mode")
|
package/src/slack-task-policy.ts
CHANGED
|
@@ -30,6 +30,14 @@ export type SlackSharedTaskOrigin = {
|
|
|
30
30
|
sourceThreadTs: string;
|
|
31
31
|
initiatingSlackUserId: string;
|
|
32
32
|
policyRevisionId: string;
|
|
33
|
+
/**
|
|
34
|
+
* Home tenancy of the frozen policy revision. The row's own
|
|
35
|
+
* `accountId`/`workspaceId` are the routed task's, which is a different
|
|
36
|
+
* workspace once Slack workspace routing is on. Null on rows written before
|
|
37
|
+
* routing existed, where the two were always equal.
|
|
38
|
+
*/
|
|
39
|
+
policyAccountId: string | null;
|
|
40
|
+
policyWorkspaceId: string | null;
|
|
33
41
|
policyHash: string;
|
|
34
42
|
policyActivationVersion: number;
|
|
35
43
|
publicationMode: "never" | "approval_required" | "allow";
|
|
@@ -310,6 +318,16 @@ export async function saveSlackSharedTaskOrigin(
|
|
|
310
318
|
.limit(1);
|
|
311
319
|
if (!existing) throw new Error("Slack shared-task origin conflict could not be resolved");
|
|
312
320
|
for (const [key, value] of Object.entries(input)) {
|
|
321
|
+
// A row written before Slack workspace routing carries no policy tenancy
|
|
322
|
+
// and implied its own, because the two could not differ then. A replay
|
|
323
|
+
// from a new image supplies it, which is not a change of evidence and
|
|
324
|
+
// must not be reported as one.
|
|
325
|
+
if (
|
|
326
|
+
(key === "policyAccountId" || key === "policyWorkspaceId") &&
|
|
327
|
+
existing[key as keyof OriginRow] === null
|
|
328
|
+
) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
313
331
|
if (existing[key as keyof OriginRow] !== value) {
|
|
314
332
|
throw new SlackTaskPolicyInvalidOperationError(
|
|
315
333
|
"Slack shared-task origin identity conflicted with immutable evidence",
|
|
@@ -6,11 +6,13 @@ import {
|
|
|
6
6
|
type SlackUserLinkAccessRequest,
|
|
7
7
|
type SlackUserLinkAccessRequestStatus,
|
|
8
8
|
} from "@opengeni/contracts";
|
|
9
|
-
import { and, asc, eq, gt, inArray, ne } from "drizzle-orm";
|
|
9
|
+
import { and, asc, eq, gt, inArray, ne, sql } from "drizzle-orm";
|
|
10
10
|
|
|
11
11
|
import { type Database, withWorkspaceRls } from "./database";
|
|
12
|
+
import { namedSubjectPersonalWorkspaceId } from "./slack-routing-personal-workspace";
|
|
12
13
|
import { withLosslessContentWriteVersion } from "./lossless-json";
|
|
13
14
|
import * as schema from "./schema";
|
|
15
|
+
import { normalizeWorkspaceMembershipPermissions } from "./workspace-membership-permissions";
|
|
14
16
|
|
|
15
17
|
type RequestRow = typeof schema.slackUserLinkAccessRequests.$inferSelect;
|
|
16
18
|
type OperationKind = "request" | "cancel" | "approve" | "deny";
|
|
@@ -51,9 +53,45 @@ export type SlackUserLinkAccessMutationInput = {
|
|
|
51
53
|
idempotencyKey: string;
|
|
52
54
|
};
|
|
53
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The Slack team one access request was raised from.
|
|
58
|
+
*
|
|
59
|
+
* The public request shape deliberately omits it, but an approver has to
|
|
60
|
+
* resolve the installation binding to know where the identity link belongs, and
|
|
61
|
+
* for a routed request that is not the workspace being granted.
|
|
62
|
+
*/
|
|
63
|
+
export async function slackUserLinkAccessRequestTeam(
|
|
64
|
+
db: Database,
|
|
65
|
+
input: { workspaceId: string; requestId: string },
|
|
66
|
+
): Promise<{ slackTeamId: string; connectionId: string } | null> {
|
|
67
|
+
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
68
|
+
const [row] = await scopedDb
|
|
69
|
+
.select({
|
|
70
|
+
slackTeamId: schema.slackUserLinkAccessRequests.slackTeamId,
|
|
71
|
+
connectionId: schema.slackUserLinkAccessRequests.connectionId,
|
|
72
|
+
})
|
|
73
|
+
.from(schema.slackUserLinkAccessRequests)
|
|
74
|
+
.where(
|
|
75
|
+
and(
|
|
76
|
+
eq(schema.slackUserLinkAccessRequests.workspaceId, input.workspaceId),
|
|
77
|
+
eq(schema.slackUserLinkAccessRequests.id, input.requestId),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
.limit(1);
|
|
81
|
+
return row ?? null;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The tenancy an identity link belongs to. See `withSlackIdentityHomeScope`.
|
|
87
|
+
*/
|
|
88
|
+
export type SlackIdentityHome = { accountId: string; workspaceId: string };
|
|
89
|
+
|
|
54
90
|
export type ApproveSlackUserLinkAccessInput = SlackUserLinkAccessMutationInput & {
|
|
55
91
|
role?: string;
|
|
56
92
|
permissions: Permission[];
|
|
93
|
+
/** The installation's tenancy, when it differs from the routed request's. */
|
|
94
|
+
identityHome?: SlackIdentityHome;
|
|
57
95
|
};
|
|
58
96
|
|
|
59
97
|
const activeStatuses: SlackUserLinkAccessRequestStatus[] = ["prepared", "pending"];
|
|
@@ -154,7 +192,13 @@ export async function prepareSlackUserLinkAccessRequest(
|
|
|
154
192
|
|
|
155
193
|
export async function getSlackUserLinkAccessRequestForSubject(
|
|
156
194
|
db: Database,
|
|
157
|
-
input: {
|
|
195
|
+
input: {
|
|
196
|
+
workspaceId: string;
|
|
197
|
+
requestId: string;
|
|
198
|
+
subjectId: string;
|
|
199
|
+
/** The installation's tenancy, when it differs from the routed request's. */
|
|
200
|
+
identityHome?: SlackIdentityHome;
|
|
201
|
+
},
|
|
158
202
|
now = new Date(),
|
|
159
203
|
): Promise<SlackUserLinkAccessRequest | null> {
|
|
160
204
|
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
@@ -318,7 +362,10 @@ export async function approveSlackUserLinkAccessRequest(
|
|
|
318
362
|
});
|
|
319
363
|
const membership = await membershipAllowsSlackLink(database, row);
|
|
320
364
|
if (!membership) throw new Error("Slack access approval did not create the required grant");
|
|
321
|
-
await
|
|
365
|
+
await withSlackIdentityHomeScope(database, row, input.identityHome, async () => {
|
|
366
|
+
await assertSlackIdentityAvailable(database, row);
|
|
367
|
+
await upsertSlackIdentityLink(database, row, input.actorSubjectId, now, input.identityHome);
|
|
368
|
+
});
|
|
322
369
|
const [updated] = await database
|
|
323
370
|
.update(schema.slackUserLinkAccessRequests)
|
|
324
371
|
.set({
|
|
@@ -362,7 +409,13 @@ export async function approveSlackUserLinkAccessRequest(
|
|
|
362
409
|
*/
|
|
363
410
|
export async function completeSlackUserLinkAccessIfGranted(
|
|
364
411
|
db: Database,
|
|
365
|
-
input: {
|
|
412
|
+
input: {
|
|
413
|
+
workspaceId: string;
|
|
414
|
+
requestId: string;
|
|
415
|
+
subjectId: string;
|
|
416
|
+
/** The installation's tenancy, when it differs from the routed request's. */
|
|
417
|
+
identityHome?: SlackIdentityHome;
|
|
418
|
+
},
|
|
366
419
|
now = new Date(),
|
|
367
420
|
): Promise<SlackUserLinkAccessRequest | null> {
|
|
368
421
|
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
@@ -388,8 +441,10 @@ export async function completeSlackUserLinkAccessIfGranted(
|
|
|
388
441
|
if (!(await membershipAllowsSlackLink(database, row))) {
|
|
389
442
|
return mapRequest(row);
|
|
390
443
|
}
|
|
391
|
-
await
|
|
392
|
-
|
|
444
|
+
await withSlackIdentityHomeScope(database, row, input.identityHome, async () => {
|
|
445
|
+
await assertSlackIdentityAvailable(database, row);
|
|
446
|
+
await upsertSlackIdentityLink(database, row, input.subjectId, now, input.identityHome);
|
|
447
|
+
});
|
|
393
448
|
const [updated] = await tx
|
|
394
449
|
.update(schema.slackUserLinkAccessRequests)
|
|
395
450
|
.set({
|
|
@@ -563,7 +618,21 @@ async function membershipAllowsSlackLink(database: Database, row: RequestRow) {
|
|
|
563
618
|
),
|
|
564
619
|
)
|
|
565
620
|
.limit(1);
|
|
566
|
-
|
|
621
|
+
if (workspaceMembershipPermissionsAllowSlackLink(membership?.permissions)) return true;
|
|
622
|
+
// A managed human's own personal workspace deliberately has no
|
|
623
|
+
// `workspace_memberships` row, so a bare membership join denies its owner.
|
|
624
|
+
// Once a Slack direct message can land there, that would tell people to
|
|
625
|
+
// request access to their own workspace. The pointer is DERIVED from an
|
|
626
|
+
// active organization membership; it is never accepted from the request row.
|
|
627
|
+
const personalWorkspaceId = await namedSubjectPersonalWorkspaceId(database, {
|
|
628
|
+
accountId: row.accountId,
|
|
629
|
+
subjectId: row.subjectId,
|
|
630
|
+
});
|
|
631
|
+
return personalWorkspaceId === row.workspaceId;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
export function workspaceMembershipPermissionsAllowSlackLink(value: unknown): boolean {
|
|
635
|
+
const permissions = normalizeWorkspaceMembershipPermissions(value);
|
|
567
636
|
return permissions.includes("sessions:create") || permissions.includes("workspace:admin");
|
|
568
637
|
}
|
|
569
638
|
|
|
@@ -584,17 +653,75 @@ async function assertSlackIdentityAvailable(database: Database, row: RequestRow)
|
|
|
584
653
|
}
|
|
585
654
|
}
|
|
586
655
|
|
|
656
|
+
/**
|
|
657
|
+
* The tenancy an identity link belongs to.
|
|
658
|
+
*
|
|
659
|
+
* `slack_bot_user_links` is unique on `(connection_id, slack_user_id)` globally
|
|
660
|
+
* but RLS-visible only under ONE workspace, and the Slack pump reads it under
|
|
661
|
+
* the installation's. A request for a ROUTED workspace runs under that routed
|
|
662
|
+
* scope, so writing the link there would file it where the pump cannot see it,
|
|
663
|
+
* and reading it there is blind to the row that already exists at home. Both the
|
|
664
|
+
* availability check and the write therefore take the installation's tenancy
|
|
665
|
+
* explicitly, and fall back to the request row's when a caller has none, which
|
|
666
|
+
* is exactly the pre-routing behaviour.
|
|
667
|
+
*/
|
|
668
|
+
function identityHomeFor(row: RequestRow, home: SlackIdentityHome | undefined): SlackIdentityHome {
|
|
669
|
+
return home ?? { accountId: row.accountId, workspaceId: row.workspaceId };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Run the identity-link statements under the installation's own tenancy.
|
|
674
|
+
*
|
|
675
|
+
* The surrounding transaction is scoped to the request's workspace, which for a
|
|
676
|
+
* routed request is NOT where the link lives, and it must stay one transaction:
|
|
677
|
+
* an approval that granted membership without linking the identity, or the
|
|
678
|
+
* reverse, would be worse than either. So the tenant GUCs are swapped for
|
|
679
|
+
* exactly these statements and restored, rather than opening a second
|
|
680
|
+
* transaction. Restoring is not optional: everything after this call still
|
|
681
|
+
* belongs to the request's workspace.
|
|
682
|
+
*/
|
|
683
|
+
async function withSlackIdentityHomeScope<T>(
|
|
684
|
+
database: Database,
|
|
685
|
+
row: RequestRow,
|
|
686
|
+
home: SlackIdentityHome | undefined,
|
|
687
|
+
fn: () => Promise<T>,
|
|
688
|
+
): Promise<T> {
|
|
689
|
+
const identity = identityHomeFor(row, home);
|
|
690
|
+
if (identity.accountId === row.accountId && identity.workspaceId === row.workspaceId) {
|
|
691
|
+
return await fn();
|
|
692
|
+
}
|
|
693
|
+
await database.execute(
|
|
694
|
+
sql`select set_config('opengeni.account_id', ${identity.accountId}, true),
|
|
695
|
+
set_config('opengeni.workspace_id', ${identity.workspaceId}, true)`,
|
|
696
|
+
);
|
|
697
|
+
try {
|
|
698
|
+
return await fn();
|
|
699
|
+
} finally {
|
|
700
|
+
// A failed statement can leave the transaction aborted and unwinding to
|
|
701
|
+
// rollback, so preserve the original diagnostic rather than replacing it
|
|
702
|
+
// with the restore's own error.
|
|
703
|
+
await database
|
|
704
|
+
.execute(
|
|
705
|
+
sql`select set_config('opengeni.account_id', ${row.accountId}, true),
|
|
706
|
+
set_config('opengeni.workspace_id', ${row.workspaceId}, true)`,
|
|
707
|
+
)
|
|
708
|
+
.catch(() => undefined);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
587
712
|
async function upsertSlackIdentityLink(
|
|
588
713
|
database: Database,
|
|
589
714
|
row: RequestRow,
|
|
590
715
|
actorSubjectId: string,
|
|
591
716
|
now: Date,
|
|
717
|
+
home?: SlackIdentityHome,
|
|
592
718
|
) {
|
|
719
|
+
const identity = identityHomeFor(row, home);
|
|
593
720
|
const [created] = await database
|
|
594
721
|
.insert(schema.slackBotUserLinks)
|
|
595
722
|
.values({
|
|
596
|
-
accountId:
|
|
597
|
-
workspaceId:
|
|
723
|
+
accountId: identity.accountId,
|
|
724
|
+
workspaceId: identity.workspaceId,
|
|
598
725
|
connectionId: row.connectionId,
|
|
599
726
|
slackTeamId: row.slackTeamId,
|
|
600
727
|
slackUserId: row.slackUserId,
|
|
@@ -624,8 +751,8 @@ async function upsertSlackIdentityLink(
|
|
|
624
751
|
await database
|
|
625
752
|
.update(schema.slackBotUserLinks)
|
|
626
753
|
.set({
|
|
627
|
-
accountId:
|
|
628
|
-
workspaceId:
|
|
754
|
+
accountId: identity.accountId,
|
|
755
|
+
workspaceId: identity.workspaceId,
|
|
629
756
|
slackTeamId: row.slackTeamId,
|
|
630
757
|
linkedBySubjectId: actorSubjectId,
|
|
631
758
|
updatedAt: now,
|
package/src/task-notes-schema.ts
CHANGED
|
@@ -133,3 +133,77 @@ export const taskNoteWriteCapabilities = pgTable(
|
|
|
133
133
|
),
|
|
134
134
|
}),
|
|
135
135
|
);
|
|
136
|
+
|
|
137
|
+
// Transaction-local, owner-only admission for the single Task-note evidence
|
|
138
|
+
// insert materialized by a governed promotion. Migration 0260 owns its RLS,
|
|
139
|
+
// one-shot trigger consumption, and runtime-role revocation.
|
|
140
|
+
export const taskNoteKnowledgePromotionCapabilities = pgTable(
|
|
141
|
+
"task_note_knowledge_promotion_capabilities",
|
|
142
|
+
{
|
|
143
|
+
backendPid: integer("backend_pid").notNull(),
|
|
144
|
+
transactionId: xid8("transaction_id").notNull(),
|
|
145
|
+
capabilityId: uuid("capability_id").notNull(),
|
|
146
|
+
accountId: uuid("account_id").notNull(),
|
|
147
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
148
|
+
noteId: uuid("note_id").notNull(),
|
|
149
|
+
rootSessionId: uuid("root_session_id").notNull(),
|
|
150
|
+
noteVersion: integer("note_version").notNull(),
|
|
151
|
+
noteTextHash: text("note_text_hash").notNull(),
|
|
152
|
+
evidenceOperationId: text("evidence_operation_id").notNull(),
|
|
153
|
+
claimOperationId: text("claim_operation_id").notNull(),
|
|
154
|
+
actorSubjectId: text("actor_subject_id").notNull(),
|
|
155
|
+
initiatingHumanSubjectId: text("initiating_human_subject_id").notNull(),
|
|
156
|
+
learningPolicySnapshotId: uuid("learning_policy_snapshot_id").notNull(),
|
|
157
|
+
},
|
|
158
|
+
(table) => ({
|
|
159
|
+
identity: uniqueIndex("task_note_knowledge_promotion_capabilities_identity_uq").on(
|
|
160
|
+
table.backendPid,
|
|
161
|
+
table.transactionId,
|
|
162
|
+
table.capabilityId,
|
|
163
|
+
),
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Immutable, content-free lineage for one atomic archive + replacement create.
|
|
168
|
+
// Migration 0260 owns exact constraints, FORCE RLS, and lifecycle-only writes.
|
|
169
|
+
export const taskNoteReplacementReceipts = pgTable(
|
|
170
|
+
"task_note_replacement_receipts",
|
|
171
|
+
{
|
|
172
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
173
|
+
accountId: uuid("account_id").notNull(),
|
|
174
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
175
|
+
rootSessionId: uuid("root_session_id").notNull(),
|
|
176
|
+
operationId: uuid("operation_id").notNull(),
|
|
177
|
+
inputHash: text("input_hash").notNull(),
|
|
178
|
+
replacedNoteId: uuid("replaced_note_id").notNull(),
|
|
179
|
+
replacedNoteVersion: integer("replaced_note_version").notNull(),
|
|
180
|
+
replacementNoteId: uuid("replacement_note_id").notNull(),
|
|
181
|
+
replacementNoteVersion: integer("replacement_note_version").notNull(),
|
|
182
|
+
archiveOperationId: uuid("archive_operation_id").notNull(),
|
|
183
|
+
createOperationId: uuid("create_operation_id").notNull(),
|
|
184
|
+
actorKind: text("actor_kind").notNull(),
|
|
185
|
+
actorSubjectId: text("actor_subject_id").notNull(),
|
|
186
|
+
initiatingHumanSubjectId: text("initiating_human_subject_id"),
|
|
187
|
+
actorSessionId: uuid("actor_session_id").notNull(),
|
|
188
|
+
actorTurnId: uuid("actor_turn_id").notNull(),
|
|
189
|
+
actorAttemptId: uuid("actor_attempt_id").notNull(),
|
|
190
|
+
actorExecutionGeneration: integer("actor_execution_generation").notNull(),
|
|
191
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
192
|
+
},
|
|
193
|
+
(table) => ({
|
|
194
|
+
workspaceOperation: uniqueIndex("task_note_replacement_receipts_workspace_operation_uq").on(
|
|
195
|
+
table.workspaceId,
|
|
196
|
+
table.operationId,
|
|
197
|
+
),
|
|
198
|
+
workspaceReplacement: uniqueIndex("task_note_replacement_receipts_workspace_replacement_uq").on(
|
|
199
|
+
table.workspaceId,
|
|
200
|
+
table.replacementNoteId,
|
|
201
|
+
),
|
|
202
|
+
rootTimeline: index("task_note_replacement_receipts_root_timeline_idx").on(
|
|
203
|
+
table.workspaceId,
|
|
204
|
+
table.rootSessionId,
|
|
205
|
+
table.createdAt,
|
|
206
|
+
table.id,
|
|
207
|
+
),
|
|
208
|
+
}),
|
|
209
|
+
);
|
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
|
}
|