@opengeni/db 6.0.0 → 6.0.1
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/canonical-human-identities.js +3 -3
- package/dist/capability-reconciliation.js +2 -2
- package/dist/child-read-reconciliation.d.ts +35 -0
- package/dist/{chunk-CJI6G254.js → chunk-4CCFFLDZ.js} +2 -2
- package/dist/{chunk-7JCDSMX2.js → chunk-4RDUZ5UV.js} +2 -2
- package/dist/{chunk-SYKHLPTK.js → chunk-6ORJPCYW.js} +2 -2
- package/dist/{chunk-GABXCG3N.js → chunk-CSVHCF3R.js} +11 -6
- package/dist/chunk-CSVHCF3R.js.map +1 -0
- package/dist/{chunk-CDAT6EKW.js → chunk-FULLE5CF.js} +3 -3
- package/dist/{chunk-MN5PZMEK.js → chunk-GSB4ISHE.js} +905 -801
- package/dist/chunk-GSB4ISHE.js.map +1 -0
- package/dist/{chunk-XIXVVBW2.js → chunk-H7BC444C.js} +3 -1
- package/dist/chunk-H7BC444C.js.map +1 -0
- package/dist/{chunk-LVY33EWL.js → chunk-I7HQL6FK.js} +3 -3
- package/dist/{chunk-RIYHP2WU.js → chunk-JILAWE26.js} +2 -2
- package/dist/{chunk-ZPJ6V2VG.js → chunk-LO6BVXXH.js} +2 -2
- package/dist/{chunk-PJI4MUZ7.js → chunk-LSLNMENV.js} +4 -4
- package/dist/{chunk-RL2MUXM3.js → chunk-PGIFHEBA.js} +2 -2
- package/dist/{chunk-G32NVMRJ.js → chunk-RXH5CFG3.js} +5 -5
- package/dist/{chunk-AJA2ZIUW.js → chunk-XK6HXON2.js} +2 -2
- package/dist/connect-attempts.js +3 -3
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +31 -0
- package/dist/index.js +6651 -6244
- package/dist/index.js.map +1 -1
- package/dist/integration-oauth-pending-states.d.ts +14 -0
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-operations.js +2 -2
- package/dist/new-session-drafts.d.ts +2 -1
- package/dist/organization-integration-policy.js +3 -3
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +6 -6
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +151 -8
- package/dist/schema.js +3 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +5 -5
- package/dist/session-event-slices.js +3 -3
- package/dist/session-mcp-credential-rotation.js +4 -4
- package/dist/session-meaningful-events.d.ts +15 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/skills.d.ts +2 -0
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +4 -4
- package/drizzle/0503_session_meaningful_attention.sql +81 -0
- package/drizzle/0504_skill_folder_limits.sql +26 -0
- package/drizzle/0505_workspace_control_revision_frontier.sql +37 -0
- package/drizzle/0506_background_command_text.sql +3 -0
- package/drizzle/0507_integration_oauth_pending_states.sql +27 -0
- package/package.json +5 -5
- package/src/child-read-reconciliation.ts +170 -0
- package/src/index.ts +439 -60
- package/src/integration-oauth-pending-states.ts +52 -0
- package/src/new-session-drafts.ts +4 -2
- package/src/runtime-posture.ts +2 -0
- package/src/schema.ts +35 -3
- package/src/session-background-commands.ts +8 -2
- package/src/session-meaningful-events.ts +97 -0
- package/src/skills.ts +14 -1
- package/dist/chunk-GABXCG3N.js.map +0 -1
- package/dist/chunk-MN5PZMEK.js.map +0 -1
- package/dist/chunk-XIXVVBW2.js.map +0 -1
- /package/dist/{chunk-CJI6G254.js.map → chunk-4CCFFLDZ.js.map} +0 -0
- /package/dist/{chunk-7JCDSMX2.js.map → chunk-4RDUZ5UV.js.map} +0 -0
- /package/dist/{chunk-SYKHLPTK.js.map → chunk-6ORJPCYW.js.map} +0 -0
- /package/dist/{chunk-CDAT6EKW.js.map → chunk-FULLE5CF.js.map} +0 -0
- /package/dist/{chunk-LVY33EWL.js.map → chunk-I7HQL6FK.js.map} +0 -0
- /package/dist/{chunk-RIYHP2WU.js.map → chunk-JILAWE26.js.map} +0 -0
- /package/dist/{chunk-ZPJ6V2VG.js.map → chunk-LO6BVXXH.js.map} +0 -0
- /package/dist/{chunk-PJI4MUZ7.js.map → chunk-LSLNMENV.js.map} +0 -0
- /package/dist/{chunk-RL2MUXM3.js.map → chunk-PGIFHEBA.js.map} +0 -0
- /package/dist/{chunk-G32NVMRJ.js.map → chunk-RXH5CFG3.js.map} +0 -0
- /package/dist/{chunk-AJA2ZIUW.js.map → chunk-XK6HXON2.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -24,6 +24,18 @@ import {
|
|
|
24
24
|
} from "@opengeni/codex";
|
|
25
25
|
export { getSessionAttemptMcpApprovalPolicies } from "./session-mcp-approval";
|
|
26
26
|
import { sessionAttemptPendingWritersSql } from "./session-attempt-writers";
|
|
27
|
+
import {
|
|
28
|
+
childLifecycleEvidenceCandidatesSql,
|
|
29
|
+
completeMeaningfulSessionEventSql,
|
|
30
|
+
meaningfulSessionEventSql,
|
|
31
|
+
meaningfulSessionSequenceSql,
|
|
32
|
+
} from "./session-meaningful-events";
|
|
33
|
+
import {
|
|
34
|
+
boundedChildLifecycleEvidence,
|
|
35
|
+
logicalChildReadEvent,
|
|
36
|
+
historicalChildReadItems,
|
|
37
|
+
historicalChildReadMatches,
|
|
38
|
+
} from "./child-read-reconciliation";
|
|
27
39
|
import { createRetryFailedSessionInTransaction } from "./session-retry";
|
|
28
40
|
export { SessionRetryConflictError, getSessionRetryReceiptInTransaction } from "./session-retry";
|
|
29
41
|
import { retainedProviderCommandPersistence as retainedProviderCommandState } from "./retained-provider-commands";
|
|
@@ -636,6 +648,10 @@ export * from "./xai-subscription";
|
|
|
636
648
|
export * from "./organization-xai-subscriptions";
|
|
637
649
|
export { interruptedToolCallResult } from "./session-tool-call-settlement";
|
|
638
650
|
export { decryptEnvironmentValue, encryptEnvironmentValue } from "./environment-crypto";
|
|
651
|
+
export {
|
|
652
|
+
loadIntegrationOAuthPendingState,
|
|
653
|
+
storeIntegrationOAuthPendingState,
|
|
654
|
+
} from "./integration-oauth-pending-states";
|
|
639
655
|
export {
|
|
640
656
|
decryptEnvironmentValue as decryptVariableSetValue,
|
|
641
657
|
encryptEnvironmentValue as encryptVariableSetValue,
|
|
@@ -7278,7 +7294,34 @@ export async function getRetainedScreenshotArtifact(
|
|
|
7278
7294
|
.where(
|
|
7279
7295
|
and(
|
|
7280
7296
|
eq(schema.retainedScreenshotArtifacts.workspaceId, workspaceId),
|
|
7281
|
-
|
|
7297
|
+
or(
|
|
7298
|
+
eq(schema.retainedScreenshotArtifacts.sessionId, sessionId),
|
|
7299
|
+
// A fork copies canonical receipts, not the original artifact row.
|
|
7300
|
+
// Require both recorded ancestry and an actually copied image receipt;
|
|
7301
|
+
// knowing an ancestor artifact UUID is not enough. All reads retain RLS.
|
|
7302
|
+
sql`exists (
|
|
7303
|
+
with recursive lineage(id, parent_id) as (
|
|
7304
|
+
select s.id, s.forked_from_session_id from sessions s
|
|
7305
|
+
where s.workspace_id = ${workspaceId} and s.id = ${sessionId}
|
|
7306
|
+
union
|
|
7307
|
+
select s.id, s.forked_from_session_id from sessions s
|
|
7308
|
+
join lineage l on s.id = l.parent_id
|
|
7309
|
+
where s.workspace_id = ${workspaceId}
|
|
7310
|
+
)
|
|
7311
|
+
select 1 from lineage l
|
|
7312
|
+
where l.id = ${schema.retainedScreenshotArtifacts.sessionId}
|
|
7313
|
+
and exists (
|
|
7314
|
+
select 1 from session_history_items h
|
|
7315
|
+
where h.workspace_id = ${workspaceId} and h.session_id = ${sessionId}
|
|
7316
|
+
and h.turn_id is null
|
|
7317
|
+
and (
|
|
7318
|
+
h.item @> ${JSON.stringify({ output: { type: "retained_artifact", artifact: { artifactId } } })}::jsonb
|
|
7319
|
+
or h.item @> ${JSON.stringify({ output: [{ image: { type: "retained_artifact", artifact: { artifactId } } }] })}::jsonb
|
|
7320
|
+
or h.item @> ${JSON.stringify({ output: { content: [{ image: { type: "retained_artifact", artifact: { artifactId } } }] } })}::jsonb
|
|
7321
|
+
)
|
|
7322
|
+
)
|
|
7323
|
+
)`,
|
|
7324
|
+
),
|
|
7282
7325
|
eq(schema.retainedScreenshotArtifacts.artifactId, artifactId),
|
|
7283
7326
|
),
|
|
7284
7327
|
)
|
|
@@ -34634,6 +34677,7 @@ type SessionPinRow = Pick<
|
|
|
34634
34677
|
| "pinnedAt"
|
|
34635
34678
|
| "version"
|
|
34636
34679
|
| "acknowledgedSequence"
|
|
34680
|
+
| "manuallyUnreadThrough"
|
|
34637
34681
|
| "activelyWorking"
|
|
34638
34682
|
| "attentionVersion"
|
|
34639
34683
|
| "archived"
|
|
@@ -34653,7 +34697,10 @@ function mapSessionPin(
|
|
|
34653
34697
|
: { pinned: false, pinnedAt: null, pinVersion: 0 };
|
|
34654
34698
|
}
|
|
34655
34699
|
|
|
34656
|
-
type SessionRow = typeof schema.sessions.$inferSelect & {
|
|
34700
|
+
type SessionRow = typeof schema.sessions.$inferSelect & {
|
|
34701
|
+
currentInputWait?: Session["inputWait"];
|
|
34702
|
+
meaningfulSequence?: number;
|
|
34703
|
+
};
|
|
34657
34704
|
|
|
34658
34705
|
/** One bounded query in the caller's RLS scope; never infer waits from history text. */
|
|
34659
34706
|
async function withCurrentSessionInputWait(
|
|
@@ -34712,9 +34759,9 @@ async function withCurrentSessionInputWait(
|
|
|
34712
34759
|
}
|
|
34713
34760
|
|
|
34714
34761
|
/**
|
|
34715
|
-
* Public sequence
|
|
34716
|
-
* raw
|
|
34717
|
-
*
|
|
34762
|
+
* Public sequence remains cursor-authoritative. Attention has its own derived
|
|
34763
|
+
* meaningful frontier; raw deltas and housekeeping must not create unread dots.
|
|
34764
|
+
* The compatibility column may lag raw deltas but cannot lead the durable cursor.
|
|
34718
34765
|
*/
|
|
34719
34766
|
async function canonicalSessionRowsFromEventCursors(
|
|
34720
34767
|
db: Database,
|
|
@@ -34729,6 +34776,10 @@ async function canonicalSessionRowsFromEventCursors(
|
|
|
34729
34776
|
workspaceId: schema.sessionEventCursors.workspaceId,
|
|
34730
34777
|
sessionId: schema.sessionEventCursors.sessionId,
|
|
34731
34778
|
lastSequence: schema.sessionEventCursors.lastSequence,
|
|
34779
|
+
meaningfulSequence: meaningfulSessionSequenceSql(
|
|
34780
|
+
schema.sessionEventCursors.workspaceId,
|
|
34781
|
+
schema.sessionEventCursors.sessionId,
|
|
34782
|
+
),
|
|
34732
34783
|
})
|
|
34733
34784
|
.from(schema.sessionEventCursors)
|
|
34734
34785
|
.where(
|
|
@@ -34751,20 +34802,24 @@ async function canonicalSessionRowsFromEventCursors(
|
|
|
34751
34802
|
) {
|
|
34752
34803
|
throw new Error(`Session event cursor invariant failed for session ${row.id}`);
|
|
34753
34804
|
}
|
|
34754
|
-
return
|
|
34755
|
-
|
|
34756
|
-
|
|
34805
|
+
return {
|
|
34806
|
+
...row,
|
|
34807
|
+
lastSequence: cursor.lastSequence,
|
|
34808
|
+
meaningfulSequence: cursor.meaningfulSequence,
|
|
34809
|
+
};
|
|
34757
34810
|
}),
|
|
34758
34811
|
);
|
|
34759
34812
|
}
|
|
34760
34813
|
|
|
34761
34814
|
function mapSessionAttention(
|
|
34762
|
-
session: Pick<
|
|
34815
|
+
session: Pick<SessionRow, "lastSequence" | "meaningfulSequence">,
|
|
34763
34816
|
row: SessionPinRow | null | undefined,
|
|
34764
34817
|
): Pick<Session, "unread" | "activelyWorking" | "attentionVersion"> {
|
|
34765
34818
|
return row
|
|
34766
34819
|
? {
|
|
34767
|
-
unread:
|
|
34820
|
+
unread:
|
|
34821
|
+
row.manuallyUnreadThrough != null ||
|
|
34822
|
+
(session.meaningfulSequence ?? session.lastSequence) > row.acknowledgedSequence,
|
|
34768
34823
|
activelyWorking: row.activelyWorking,
|
|
34769
34824
|
attentionVersion: Number(row.attentionVersion),
|
|
34770
34825
|
}
|
|
@@ -34772,7 +34827,7 @@ function mapSessionAttention(
|
|
|
34772
34827
|
// An absent personal row means this subject has never acknowledged a
|
|
34773
34828
|
// completion. Existing and newly discovered finished work therefore
|
|
34774
34829
|
// remains visible until the member explicitly marks it read.
|
|
34775
|
-
unread: session.lastSequence > 0,
|
|
34830
|
+
unread: (session.meaningfulSequence ?? session.lastSequence) > 0,
|
|
34776
34831
|
activelyWorking: false,
|
|
34777
34832
|
attentionVersion: 0,
|
|
34778
34833
|
};
|
|
@@ -34978,7 +35033,7 @@ export async function sessionTreeStatsForSessions(
|
|
|
34978
35033
|
select
|
|
34979
35034
|
root.id,
|
|
34980
35035
|
root.status,
|
|
34981
|
-
|
|
35036
|
+
${meaningfulSessionSequenceSql(sql`root.workspace_id`, sql`root.id`)},
|
|
34982
35037
|
greatest(
|
|
34983
35038
|
case
|
|
34984
35039
|
when workspace_control.workspace_state = 'paused'
|
|
@@ -35003,7 +35058,7 @@ export async function sessionTreeStatsForSessions(
|
|
|
35003
35058
|
select
|
|
35004
35059
|
child.id,
|
|
35005
35060
|
child.status,
|
|
35006
|
-
|
|
35061
|
+
${meaningfulSessionSequenceSql(sql`child.workspace_id`, sql`child.id`)},
|
|
35007
35062
|
greatest(
|
|
35008
35063
|
case
|
|
35009
35064
|
-- A subtree resume defeats every inherited pause older than it.
|
|
@@ -35106,7 +35161,7 @@ export async function sessionTreeStatsForSessions(
|
|
|
35106
35161
|
and status = 'failed'
|
|
35107
35162
|
and ${subjectId ?? null}::text is not null
|
|
35108
35163
|
and last_sequence > coalesce((
|
|
35109
|
-
select personal.acknowledged_sequence
|
|
35164
|
+
select case when personal.manually_unread_through is not null then -1 else personal.acknowledged_sequence end
|
|
35110
35165
|
from ${schema.sessionPins} personal
|
|
35111
35166
|
where personal.workspace_id = ${workspaceId}
|
|
35112
35167
|
and personal.subject_id = ${subjectId ?? null}
|
|
@@ -35118,7 +35173,7 @@ export async function sessionTreeStatsForSessions(
|
|
|
35118
35173
|
and depth > 0
|
|
35119
35174
|
and ${subjectId ?? null}::text is not null
|
|
35120
35175
|
and last_sequence > coalesce((
|
|
35121
|
-
select personal.acknowledged_sequence
|
|
35176
|
+
select case when personal.manually_unread_through is not null then -1 else personal.acknowledged_sequence end
|
|
35122
35177
|
from ${schema.sessionPins} personal
|
|
35123
35178
|
where personal.workspace_id = ${workspaceId}
|
|
35124
35179
|
and personal.subject_id = ${subjectId ?? null}
|
|
@@ -36368,6 +36423,10 @@ export async function getSessionForSubject(
|
|
|
36368
36423
|
session: schema.sessions,
|
|
36369
36424
|
pin: schema.sessionPins,
|
|
36370
36425
|
cursor: schema.sessionEventCursors,
|
|
36426
|
+
meaningfulSequence: meaningfulSessionSequenceSql(
|
|
36427
|
+
schema.sessions.workspaceId,
|
|
36428
|
+
schema.sessions.id,
|
|
36429
|
+
),
|
|
36371
36430
|
})
|
|
36372
36431
|
.from(schema.sessions)
|
|
36373
36432
|
// Status and replay cursor must share one statement snapshot. Otherwise a
|
|
@@ -36415,7 +36474,11 @@ export async function getSessionForSubject(
|
|
|
36415
36474
|
scopedDb,
|
|
36416
36475
|
workspaceId,
|
|
36417
36476
|
await withCurrentSessionInputWait(scopedDb, workspaceId, [
|
|
36418
|
-
{
|
|
36477
|
+
{
|
|
36478
|
+
...row.session,
|
|
36479
|
+
lastSequence: row.cursor.lastSequence,
|
|
36480
|
+
meaningfulSequence: row.meaningfulSequence,
|
|
36481
|
+
},
|
|
36419
36482
|
]),
|
|
36420
36483
|
);
|
|
36421
36484
|
if (!session) throw new Error(`Session event cursor missing for session ${sessionId}`);
|
|
@@ -36652,9 +36715,7 @@ export async function setSessionAttention(
|
|
|
36652
36715
|
input.unread === undefined
|
|
36653
36716
|
? currentAcknowledgedSequence
|
|
36654
36717
|
: input.unread
|
|
36655
|
-
?
|
|
36656
|
-
? currentAcknowledgedSequence
|
|
36657
|
-
: Math.max(-1, session.lastSequence - 1)
|
|
36718
|
+
? -1
|
|
36658
36719
|
: Math.max(
|
|
36659
36720
|
currentAcknowledgedSequence,
|
|
36660
36721
|
Math.min(
|
|
@@ -36663,8 +36724,15 @@ export async function setSessionAttention(
|
|
|
36663
36724
|
),
|
|
36664
36725
|
);
|
|
36665
36726
|
const desiredActivelyWorking = input.activelyWorking ?? current.activelyWorking;
|
|
36727
|
+
const manuallyUnreadThrough =
|
|
36728
|
+
input.unread === undefined
|
|
36729
|
+
? (existing?.manuallyUnreadThrough ?? null)
|
|
36730
|
+
: input.unread
|
|
36731
|
+
? session.lastSequence
|
|
36732
|
+
: null;
|
|
36666
36733
|
if (
|
|
36667
36734
|
acknowledgedSequence === currentAcknowledgedSequence &&
|
|
36735
|
+
manuallyUnreadThrough === (existing?.manuallyUnreadThrough ?? null) &&
|
|
36668
36736
|
desiredActivelyWorking === current.activelyWorking
|
|
36669
36737
|
) {
|
|
36670
36738
|
const mcpServers = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
|
|
@@ -36709,6 +36777,7 @@ export async function setSessionAttention(
|
|
|
36709
36777
|
pinnedAt: null,
|
|
36710
36778
|
version: 0,
|
|
36711
36779
|
acknowledgedSequence,
|
|
36780
|
+
manuallyUnreadThrough,
|
|
36712
36781
|
activelyWorking: desiredActivelyWorking,
|
|
36713
36782
|
attentionVersion: 1,
|
|
36714
36783
|
archiveVersion: 0,
|
|
@@ -36721,6 +36790,7 @@ export async function setSessionAttention(
|
|
|
36721
36790
|
],
|
|
36722
36791
|
set: {
|
|
36723
36792
|
acknowledgedSequence: acknowledgedSequenceWrite,
|
|
36793
|
+
manuallyUnreadThrough,
|
|
36724
36794
|
activelyWorking: desiredActivelyWorking,
|
|
36725
36795
|
attentionVersion: sql`${schema.sessionPins.attentionVersion} + 1`,
|
|
36726
36796
|
},
|
|
@@ -36732,6 +36802,7 @@ export async function setSessionAttention(
|
|
|
36732
36802
|
.update(schema.sessionPins)
|
|
36733
36803
|
.set({
|
|
36734
36804
|
acknowledgedSequence: acknowledgedSequenceWrite,
|
|
36805
|
+
manuallyUnreadThrough,
|
|
36735
36806
|
activelyWorking: desiredActivelyWorking,
|
|
36736
36807
|
attentionVersion: sql`${schema.sessionPins.attentionVersion} + 1`,
|
|
36737
36808
|
})
|
|
@@ -66167,38 +66238,56 @@ function isChildLifecycleSystemUpdatePayload(
|
|
|
66167
66238
|
function consumedChildLifecycleSessionIds(
|
|
66168
66239
|
updates: ReadonlyArray<typeof schema.sessionSystemUpdates.$inferSelect>,
|
|
66169
66240
|
payloadsById: ReadonlyMap<string, SessionSystemUpdatePayload>,
|
|
66170
|
-
):
|
|
66171
|
-
const
|
|
66241
|
+
): ConsumedChildEvidence[] {
|
|
66242
|
+
const children: ConsumedChildEvidence[] = [];
|
|
66172
66243
|
for (const update of updates) {
|
|
66173
66244
|
if (!isChildLifecycleSystemUpdateKind(update.kind)) continue;
|
|
66174
66245
|
const payload = payloadsById.get(update.id);
|
|
66175
66246
|
// The row's kind and its payload discriminator are written together; narrow
|
|
66176
66247
|
// on the discriminator so the shared field access is typed rather than cast.
|
|
66177
66248
|
if (!payload || !isChildLifecycleSystemUpdatePayload(payload)) continue;
|
|
66178
|
-
|
|
66249
|
+
const evidence = payload.childEventEvidence;
|
|
66250
|
+
if (!Array.isArray(evidence) || evidence.length > 32) continue;
|
|
66251
|
+
for (const event of evidence) {
|
|
66252
|
+
if (
|
|
66253
|
+
event &&
|
|
66254
|
+
typeof event === "object" &&
|
|
66255
|
+
Number.isSafeInteger(event.sequence) &&
|
|
66256
|
+
event.sequence > 0 &&
|
|
66257
|
+
event.sequence <= POSTGRES_INT_MAX &&
|
|
66258
|
+
typeof event.type === "string" &&
|
|
66259
|
+
"payload" in event
|
|
66260
|
+
) {
|
|
66261
|
+
children.push({
|
|
66262
|
+
sessionId: payload.childSessionId,
|
|
66263
|
+
sequence: event.sequence,
|
|
66264
|
+
type: event.type,
|
|
66265
|
+
payload: event.payload,
|
|
66266
|
+
});
|
|
66267
|
+
}
|
|
66268
|
+
}
|
|
66179
66269
|
}
|
|
66180
|
-
return
|
|
66270
|
+
return children;
|
|
66181
66271
|
}
|
|
66182
66272
|
|
|
66273
|
+
type ConsumedChildEvidence = {
|
|
66274
|
+
sessionId: string;
|
|
66275
|
+
sequence: number;
|
|
66276
|
+
type: string;
|
|
66277
|
+
payload: unknown;
|
|
66278
|
+
};
|
|
66279
|
+
|
|
66183
66280
|
/**
|
|
66184
66281
|
* Acknowledge every child whose lifecycle notice this claim just turned into
|
|
66185
66282
|
* durable model input, for the receiving turn's frozen initiating human only.
|
|
66186
66283
|
*
|
|
66187
|
-
*
|
|
66188
|
-
*
|
|
66189
|
-
*
|
|
66190
|
-
*
|
|
66191
|
-
*
|
|
66192
|
-
*
|
|
66193
|
-
*
|
|
66194
|
-
*
|
|
66195
|
-
* `failed` and `requires_action` indicators are derived from `sessions.status`
|
|
66196
|
-
* and rank above unread, so an acknowledged child that still needs a human keeps
|
|
66197
|
-
* saying so, and a child that emits a further event goes unread again on its own
|
|
66198
|
-
* because unread is only the sequence comparison in {@link mapSessionAttention}.
|
|
66199
|
-
* The fence is monotone, so it also advances past a human's earlier explicit
|
|
66200
|
-
* mark-unread once the parent consumes a newer notice: consumption is the
|
|
66201
|
-
* signal, and OpenGeni keeps no durable "keep this unread" intent.
|
|
66284
|
+
* Only complete immutable event evidence carried in the claimed payload counts.
|
|
66285
|
+
* A terminal status alone does not prove that the parent saw the child's answer.
|
|
66286
|
+
* Pure service turns and legacy notices without content evidence write nothing.
|
|
66287
|
+
* A complete final answer is a cumulative read watermark. Other evidence advances
|
|
66288
|
+
* only a contiguous meaningful prefix. The shared writer preserves newer
|
|
66289
|
+
* personal attention mutations, and never reads the child's current raw cursor
|
|
66290
|
+
* as consumption evidence. Failed/requires_action lifecycle indicators remain.
|
|
66202
66291
|
*
|
|
66203
66292
|
* This deliberately does NOT touch `attention_version`. That revision exists
|
|
66204
66293
|
* only to order explicit human attention mutations against each other, and this
|
|
@@ -66234,13 +66323,12 @@ function consumedChildLifecycleSessionIds(
|
|
|
66234
66323
|
* The upsert is monotone: the conflict path re-reads the committed row under its
|
|
66235
66324
|
* own row lock and refuses to move a fence backward.
|
|
66236
66325
|
*
|
|
66237
|
-
*
|
|
66238
|
-
*
|
|
66239
|
-
*
|
|
66240
|
-
*
|
|
66241
|
-
*
|
|
66242
|
-
*
|
|
66243
|
-
* does not have. Both RLS policies on `session_pins` still apply through the
|
|
66326
|
+
* Current authority is rechecked inside the advisory fence, so historical replay
|
|
66327
|
+
* after membership removal cannot recreate deleted personal state. Frozen turn
|
|
66328
|
+
* provenance identifies the human; the canonical live-authority resolver only
|
|
66329
|
+
* checks that human's still-active shared membership or exact Personal pointer,
|
|
66330
|
+
* and is not used to authorize a caller or grant access. No creator fallback or
|
|
66331
|
+
* request-derived human is accepted. Both RLS policies on `session_pins` still apply through the
|
|
66244
66332
|
* temporary subject scope, and the insert is additionally fenced on
|
|
66245
66333
|
* `parent_session_id`, so a payload field can never decide whose personal state
|
|
66246
66334
|
* is mutated on an unrelated session.
|
|
@@ -66251,11 +66339,239 @@ async function acknowledgeConsumedChildLifecycleNotices(
|
|
|
66251
66339
|
workspaceId: string;
|
|
66252
66340
|
sessionId: string;
|
|
66253
66341
|
subjectId: string | null;
|
|
66254
|
-
childSessionIds: readonly
|
|
66342
|
+
childSessionIds: readonly ConsumedChildEvidence[];
|
|
66255
66343
|
},
|
|
66256
66344
|
): Promise<void> {
|
|
66257
66345
|
const subjectId = input.subjectId?.trim();
|
|
66258
66346
|
if (!subjectId || input.childSessionIds.length === 0) return;
|
|
66347
|
+
// Payloads are evidence, not authority. Verify every complete snapshot against
|
|
66348
|
+
// its immutable child event before accepting the claimed model-input receipt.
|
|
66349
|
+
const proven = new Map<string, number[]>();
|
|
66350
|
+
const grouped = new Map<string, ConsumedChildEvidence[]>();
|
|
66351
|
+
for (const evidence of input.childSessionIds) {
|
|
66352
|
+
const group = grouped.get(evidence.sessionId) ?? [];
|
|
66353
|
+
group.push(evidence);
|
|
66354
|
+
grouped.set(evidence.sessionId, group);
|
|
66355
|
+
}
|
|
66356
|
+
for (const [childId, evidence] of grouped) {
|
|
66357
|
+
const events = await tx
|
|
66358
|
+
.select({
|
|
66359
|
+
sequence: schema.sessionEvents.sequence,
|
|
66360
|
+
type: schema.sessionEvents.type,
|
|
66361
|
+
payload: schema.sessionEvents.payload,
|
|
66362
|
+
payloadCodecVersion: schema.sessionEvents.payloadCodecVersion,
|
|
66363
|
+
})
|
|
66364
|
+
.from(schema.sessionEvents)
|
|
66365
|
+
.innerJoin(
|
|
66366
|
+
schema.sessions,
|
|
66367
|
+
and(
|
|
66368
|
+
eq(schema.sessions.workspaceId, schema.sessionEvents.workspaceId),
|
|
66369
|
+
eq(schema.sessions.id, schema.sessionEvents.sessionId),
|
|
66370
|
+
eq(schema.sessions.parentSessionId, input.sessionId),
|
|
66371
|
+
),
|
|
66372
|
+
)
|
|
66373
|
+
.where(
|
|
66374
|
+
and(
|
|
66375
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
66376
|
+
eq(schema.sessionEvents.sessionId, childId),
|
|
66377
|
+
inArray(schema.sessionEvents.sequence, [
|
|
66378
|
+
...new Set(evidence.map((event) => event.sequence)),
|
|
66379
|
+
]),
|
|
66380
|
+
),
|
|
66381
|
+
);
|
|
66382
|
+
const bySequence = new Map(
|
|
66383
|
+
events.map((event) => [event.sequence, logicalChildReadEvent(event)]),
|
|
66384
|
+
);
|
|
66385
|
+
const sequences = evidence
|
|
66386
|
+
.filter((proof) => {
|
|
66387
|
+
const event = bySequence.get(proof.sequence);
|
|
66388
|
+
return event?.type === proof.type && isDeepStrictEqual(event.payload, proof.payload);
|
|
66389
|
+
})
|
|
66390
|
+
.map((event) => event.sequence);
|
|
66391
|
+
if (sequences.length > 0) proven.set(childId, sequences);
|
|
66392
|
+
}
|
|
66393
|
+
await acknowledgeConsumedChildSequencesInTransaction(tx, {
|
|
66394
|
+
...input,
|
|
66395
|
+
children: [...proven].map(([sessionId, sequences]) => ({ sessionId, sequences })),
|
|
66396
|
+
});
|
|
66397
|
+
}
|
|
66398
|
+
|
|
66399
|
+
/** Internal read receipt. Call only after the complete bounded response has been
|
|
66400
|
+
* constructed and the exact caller attempt's frozen human reauthorized. */
|
|
66401
|
+
export async function acknowledgeConsumedChildEvents(
|
|
66402
|
+
db: Database,
|
|
66403
|
+
input: {
|
|
66404
|
+
workspaceId: string;
|
|
66405
|
+
sessionId: string;
|
|
66406
|
+
subjectId: string | null;
|
|
66407
|
+
children: readonly { sessionId: string; sequences: readonly number[] }[];
|
|
66408
|
+
},
|
|
66409
|
+
): Promise<void> {
|
|
66410
|
+
await withWorkspaceRls(db, input.workspaceId, async (tx) => {
|
|
66411
|
+
await acknowledgeConsumedChildSequencesInTransaction(tx, input);
|
|
66412
|
+
});
|
|
66413
|
+
}
|
|
66414
|
+
|
|
66415
|
+
/** Bounded operator reconciliation, dry-run by default. No timestamps, session
|
|
66416
|
+
* creator or latest human are evidence of consumption. Unrecognized historical
|
|
66417
|
+
* providers/Codemode layouts remain untouched rather than guessed. */
|
|
66418
|
+
export async function reconcileHistoricalChildReadAcknowledgments(
|
|
66419
|
+
db: Database,
|
|
66420
|
+
input: {
|
|
66421
|
+
workspaceId: string;
|
|
66422
|
+
parentSessionId: string;
|
|
66423
|
+
afterSequence?: number;
|
|
66424
|
+
limit?: number;
|
|
66425
|
+
apply?: boolean;
|
|
66426
|
+
},
|
|
66427
|
+
): Promise<{
|
|
66428
|
+
scanned: number;
|
|
66429
|
+
provenEvents: number;
|
|
66430
|
+
unsupportedReceipts: number;
|
|
66431
|
+
nextAfter: number;
|
|
66432
|
+
hasMore: boolean;
|
|
66433
|
+
applied: boolean;
|
|
66434
|
+
}> {
|
|
66435
|
+
const limit = Math.max(1, Math.min(100, input.limit ?? 50));
|
|
66436
|
+
return await withWorkspaceRls(db, input.workspaceId, async (tx) => {
|
|
66437
|
+
const outputs = await tx
|
|
66438
|
+
.select()
|
|
66439
|
+
.from(schema.sessionEvents)
|
|
66440
|
+
.where(
|
|
66441
|
+
and(
|
|
66442
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
66443
|
+
eq(schema.sessionEvents.sessionId, input.parentSessionId),
|
|
66444
|
+
eq(schema.sessionEvents.type, "agent.toolCall.output"),
|
|
66445
|
+
gt(schema.sessionEvents.sequence, input.afterSequence ?? 0),
|
|
66446
|
+
isNull(schema.sessionEvents.duplicateOfEventId),
|
|
66447
|
+
eq(schema.sessionEvents.turnAssociation, "current"),
|
|
66448
|
+
sql`octet_length(${schema.sessionEvents.payload}::text) <= 262144`,
|
|
66449
|
+
),
|
|
66450
|
+
)
|
|
66451
|
+
.orderBy(asc(schema.sessionEvents.sequence))
|
|
66452
|
+
.limit(limit + 1);
|
|
66453
|
+
let provenEvents = 0;
|
|
66454
|
+
let unsupportedReceipts = 0;
|
|
66455
|
+
for (const output of outputs.slice(0, limit)) {
|
|
66456
|
+
const payload = logicalChildReadEvent(output).payload as Record<string, unknown> | null;
|
|
66457
|
+
if (!output.turnId || !payload || typeof payload.id !== "string" || payload.truncation) {
|
|
66458
|
+
unsupportedReceipts += 1;
|
|
66459
|
+
continue;
|
|
66460
|
+
}
|
|
66461
|
+
const [turn] = await tx
|
|
66462
|
+
.select()
|
|
66463
|
+
.from(schema.sessionTurns)
|
|
66464
|
+
.where(
|
|
66465
|
+
and(
|
|
66466
|
+
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
66467
|
+
eq(schema.sessionTurns.sessionId, input.parentSessionId),
|
|
66468
|
+
eq(schema.sessionTurns.id, output.turnId),
|
|
66469
|
+
),
|
|
66470
|
+
)
|
|
66471
|
+
.limit(1);
|
|
66472
|
+
const subjectId =
|
|
66473
|
+
turn?.initiatingHumanSubjectId ??
|
|
66474
|
+
(turn?.initiatorKind === "subject" ? turn.initiatorSubjectId : null);
|
|
66475
|
+
if (!subjectId) {
|
|
66476
|
+
unsupportedReceipts += 1;
|
|
66477
|
+
continue;
|
|
66478
|
+
}
|
|
66479
|
+
const calls = await tx
|
|
66480
|
+
.select()
|
|
66481
|
+
.from(schema.sessionEvents)
|
|
66482
|
+
.where(
|
|
66483
|
+
and(
|
|
66484
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
66485
|
+
eq(schema.sessionEvents.sessionId, input.parentSessionId),
|
|
66486
|
+
eq(schema.sessionEvents.turnId, output.turnId),
|
|
66487
|
+
eq(schema.sessionEvents.type, "agent.toolCall.created"),
|
|
66488
|
+
eq(schema.sessionEvents.turnAssociation, "current"),
|
|
66489
|
+
isNull(schema.sessionEvents.duplicateOfEventId),
|
|
66490
|
+
lt(schema.sessionEvents.sequence, output.sequence),
|
|
66491
|
+
sql`${schema.sessionEvents.payload} ->> 'id' = ${payload.id}`,
|
|
66492
|
+
sql`octet_length(${schema.sessionEvents.payload}::text) <= 16384`,
|
|
66493
|
+
),
|
|
66494
|
+
)
|
|
66495
|
+
.limit(2);
|
|
66496
|
+
if (calls.length !== 1) {
|
|
66497
|
+
unsupportedReceipts += 1;
|
|
66498
|
+
continue;
|
|
66499
|
+
}
|
|
66500
|
+
const call = logicalChildReadEvent(calls[0]!).payload as Record<string, unknown>;
|
|
66501
|
+
const candidates = historicalChildReadItems(call.name, call.arguments, payload.output);
|
|
66502
|
+
const proven: ConsumedChildEvidence[] = [];
|
|
66503
|
+
for (const candidate of candidates.slice(0, 100)) {
|
|
66504
|
+
if (
|
|
66505
|
+
!UUID_PATTERN.test(candidate.sessionId) ||
|
|
66506
|
+
!Number.isSafeInteger(candidate.item.sequence) ||
|
|
66507
|
+
(candidate.item.sequence as number) < 1 ||
|
|
66508
|
+
(candidate.item.sequence as number) > POSTGRES_INT_MAX
|
|
66509
|
+
)
|
|
66510
|
+
continue;
|
|
66511
|
+
const [source] = await tx
|
|
66512
|
+
.select({ event: schema.sessionEvents })
|
|
66513
|
+
.from(schema.sessionEvents)
|
|
66514
|
+
.innerJoin(
|
|
66515
|
+
schema.sessions,
|
|
66516
|
+
and(
|
|
66517
|
+
eq(schema.sessions.workspaceId, schema.sessionEvents.workspaceId),
|
|
66518
|
+
eq(schema.sessions.id, schema.sessionEvents.sessionId),
|
|
66519
|
+
eq(schema.sessions.parentSessionId, input.parentSessionId),
|
|
66520
|
+
),
|
|
66521
|
+
)
|
|
66522
|
+
.where(
|
|
66523
|
+
and(
|
|
66524
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
66525
|
+
eq(schema.sessionEvents.sessionId, candidate.sessionId),
|
|
66526
|
+
eq(schema.sessionEvents.sequence, candidate.item.sequence as number),
|
|
66527
|
+
completeMeaningfulSessionEventSql("session_events"),
|
|
66528
|
+
sql`octet_length(${schema.sessionEvents.payload}::text) <= 262144`,
|
|
66529
|
+
),
|
|
66530
|
+
)
|
|
66531
|
+
.limit(1);
|
|
66532
|
+
if (!source) continue;
|
|
66533
|
+
const logicalEvent = logicalChildReadEvent(source.event);
|
|
66534
|
+
if (!historicalChildReadMatches(candidate, logicalEvent)) continue;
|
|
66535
|
+
proven.push({
|
|
66536
|
+
sessionId: candidate.sessionId,
|
|
66537
|
+
sequence: source.event.sequence,
|
|
66538
|
+
type: source.event.type,
|
|
66539
|
+
payload: logicalEvent.payload,
|
|
66540
|
+
});
|
|
66541
|
+
}
|
|
66542
|
+
if (proven.length === 0) unsupportedReceipts += 1;
|
|
66543
|
+
provenEvents += proven.length;
|
|
66544
|
+
if (input.apply && proven.length > 0) {
|
|
66545
|
+
await acknowledgeConsumedChildLifecycleNotices(tx, {
|
|
66546
|
+
workspaceId: input.workspaceId,
|
|
66547
|
+
sessionId: input.parentSessionId,
|
|
66548
|
+
subjectId,
|
|
66549
|
+
childSessionIds: proven,
|
|
66550
|
+
});
|
|
66551
|
+
}
|
|
66552
|
+
}
|
|
66553
|
+
return {
|
|
66554
|
+
scanned: Math.min(limit, outputs.length),
|
|
66555
|
+
provenEvents,
|
|
66556
|
+
unsupportedReceipts,
|
|
66557
|
+
nextAfter: outputs[Math.min(limit, outputs.length) - 1]?.sequence ?? input.afterSequence ?? 0,
|
|
66558
|
+
hasMore: outputs.length > limit,
|
|
66559
|
+
applied: input.apply === true,
|
|
66560
|
+
};
|
|
66561
|
+
});
|
|
66562
|
+
}
|
|
66563
|
+
|
|
66564
|
+
async function acknowledgeConsumedChildSequencesInTransaction(
|
|
66565
|
+
tx: Database,
|
|
66566
|
+
input: {
|
|
66567
|
+
workspaceId: string;
|
|
66568
|
+
sessionId: string;
|
|
66569
|
+
subjectId: string | null;
|
|
66570
|
+
children: readonly { sessionId: string; sequences: readonly number[] }[];
|
|
66571
|
+
},
|
|
66572
|
+
): Promise<void> {
|
|
66573
|
+
const subjectId = input.subjectId?.trim();
|
|
66574
|
+
if (!subjectId || input.children.length === 0) return;
|
|
66259
66575
|
// SHARED, not exclusive. `listSessionsForSubject` holds the shared counterpart
|
|
66260
66576
|
// of this fence for its whole rail-list transaction, and that list refreshes on
|
|
66261
66577
|
// focus, online, and visibilitychange. An exclusive probe fails against a held
|
|
@@ -66275,9 +66591,47 @@ async function acknowledgeConsumedChildLifecycleNotices(
|
|
|
66275
66591
|
if (!fence?.acquired) return;
|
|
66276
66592
|
const pins = schema.sessionPins;
|
|
66277
66593
|
const sessions = schema.sessions;
|
|
66278
|
-
const cursors = schema.sessionEventCursors;
|
|
66279
66594
|
await withTemporarySubjectRls(tx, subjectId, async () => {
|
|
66280
|
-
|
|
66595
|
+
if (
|
|
66596
|
+
!(await subjectHasLiveWorkspaceAuthorityInScope(tx, {
|
|
66597
|
+
accountId: await accountIdInRlsScope(tx),
|
|
66598
|
+
workspaceId: input.workspaceId,
|
|
66599
|
+
subjectId,
|
|
66600
|
+
}))
|
|
66601
|
+
)
|
|
66602
|
+
return;
|
|
66603
|
+
// Stable UUID order preserves the personal-row lock order. A complete final
|
|
66604
|
+
// answer summarizes earlier work; nonfinal filtered reads cannot skip holes.
|
|
66605
|
+
for (const child of [...input.children].sort((a, b) =>
|
|
66606
|
+
a.sessionId.localeCompare(b.sessionId),
|
|
66607
|
+
)) {
|
|
66608
|
+
const requested = [
|
|
66609
|
+
...new Set(
|
|
66610
|
+
child.sequences.filter(
|
|
66611
|
+
(value) => Number.isSafeInteger(value) && value > 0 && value <= POSTGRES_INT_MAX,
|
|
66612
|
+
),
|
|
66613
|
+
),
|
|
66614
|
+
];
|
|
66615
|
+
if (requested.length === 0) continue;
|
|
66616
|
+
const events = await tx
|
|
66617
|
+
.select({ sequence: schema.sessionEvents.sequence, type: schema.sessionEvents.type })
|
|
66618
|
+
.from(schema.sessionEvents)
|
|
66619
|
+
.where(
|
|
66620
|
+
and(
|
|
66621
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
66622
|
+
eq(schema.sessionEvents.sessionId, child.sessionId),
|
|
66623
|
+
inArray(schema.sessionEvents.sequence, requested),
|
|
66624
|
+
completeMeaningfulSessionEventSql("session_events"),
|
|
66625
|
+
),
|
|
66626
|
+
);
|
|
66627
|
+
const sequences = events.map((event) => event.sequence);
|
|
66628
|
+
if (sequences.length === 0) continue; // status-only reads do not acknowledge
|
|
66629
|
+
const through = Math.max(...sequences);
|
|
66630
|
+
const finalThrough = Math.max(
|
|
66631
|
+
0,
|
|
66632
|
+
...events.filter((event) => event.type === "turn.completed").map((event) => event.sequence),
|
|
66633
|
+
);
|
|
66634
|
+
await tx.execute(sql`
|
|
66281
66635
|
insert into ${pins} (
|
|
66282
66636
|
account_id, workspace_id, subject_id, session_id,
|
|
66283
66637
|
pinned, pinned_at, version,
|
|
@@ -66290,26 +66644,37 @@ async function acknowledgeConsumedChildLifecycleNotices(
|
|
|
66290
66644
|
-- at the absent-row projection so a concurrent pin/archive writer
|
|
66291
66645
|
-- holding that projection stays valid.
|
|
66292
66646
|
false, null, 0,
|
|
66293
|
-
${
|
|
66647
|
+
least(${through}, coalesce((
|
|
66648
|
+
select min(unseen.sequence) - 1 from session_events unseen
|
|
66649
|
+
where unseen.workspace_id = ${sessions.workspaceId} and unseen.session_id = ${sessions.id}
|
|
66650
|
+
and unseen.sequence > greatest(
|
|
66651
|
+
coalesce(personal.acknowledged_sequence, 0),
|
|
66652
|
+
coalesce(personal.manually_unread_through, 0), ${finalThrough})
|
|
66653
|
+
and unseen.sequence <= ${through}
|
|
66654
|
+
and ${meaningfulSessionEventSql("unseen")}
|
|
66655
|
+
and unseen.sequence not in (${sql.join(
|
|
66656
|
+
sequences.map((sequence) => sql`${sequence}`),
|
|
66657
|
+
sql`, `,
|
|
66658
|
+
)})
|
|
66659
|
+
), ${through})), false, 0,
|
|
66294
66660
|
false, null, 0
|
|
66295
66661
|
from ${sessions}
|
|
66296
|
-
join ${
|
|
66297
|
-
|
|
66298
|
-
and ${cursors.workspaceId} = ${sessions.workspaceId}
|
|
66299
|
-
and ${cursors.sessionId} = ${sessions.id}
|
|
66662
|
+
left join ${pins} personal on personal.workspace_id = ${sessions.workspaceId}
|
|
66663
|
+
and personal.session_id = ${sessions.id} and personal.subject_id = ${subjectId}
|
|
66300
66664
|
where ${and(
|
|
66301
66665
|
eq(sessions.workspaceId, input.workspaceId),
|
|
66302
66666
|
eq(sessions.parentSessionId, input.sessionId),
|
|
66303
|
-
|
|
66304
|
-
// Nothing to acknowledge on a child with no durable events: the
|
|
66305
|
-
// absent-row projection already reports it read.
|
|
66306
|
-
gt(cursors.lastSequence, 0),
|
|
66667
|
+
eq(sessions.id, child.sessionId),
|
|
66307
66668
|
)}
|
|
66669
|
+
and (personal.manually_unread_through is null or ${through} > personal.manually_unread_through)
|
|
66308
66670
|
order by ${sessions.id}
|
|
66309
66671
|
on conflict (subject_id, workspace_id, session_id) do update
|
|
66310
|
-
set acknowledged_sequence = excluded.acknowledged_sequence
|
|
66672
|
+
set acknowledged_sequence = excluded.acknowledged_sequence,
|
|
66673
|
+
manually_unread_through = null
|
|
66311
66674
|
where ${pins.acknowledgedSequence} < excluded.acknowledged_sequence
|
|
66675
|
+
and (${pins.manuallyUnreadThrough} is null or excluded.acknowledged_sequence > ${pins.manuallyUnreadThrough})
|
|
66312
66676
|
`);
|
|
66677
|
+
}
|
|
66313
66678
|
});
|
|
66314
66679
|
}
|
|
66315
66680
|
|
|
@@ -66385,7 +66750,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
66385
66750
|
historyItem: Record<string, unknown> | null;
|
|
66386
66751
|
updates: Array<typeof schema.sessionSystemUpdates.$inferSelect>;
|
|
66387
66752
|
/** Deduped children the delivered batch reports on, in delivery order. */
|
|
66388
|
-
childSessionIds:
|
|
66753
|
+
childSessionIds: ConsumedChildEvidence[];
|
|
66389
66754
|
events: SessionEventInsertWithPayload[];
|
|
66390
66755
|
event: SessionEventInsertWithPayload | null;
|
|
66391
66756
|
}> => {
|
|
@@ -75751,6 +76116,20 @@ async function enqueueChildLifecycleNoticeOutboxTx(
|
|
|
75751
76116
|
...session,
|
|
75752
76117
|
parentSessionId: session.parentSessionId,
|
|
75753
76118
|
});
|
|
76119
|
+
// Freeze complete content at notice creation, never at parent claim time.
|
|
76120
|
+
// Oversized evidence is omitted, not truncated and then called consumed.
|
|
76121
|
+
// Bound index candidates before payload filters, and prefer the newest complete
|
|
76122
|
+
// result within the total budget. Explicit reads handle omitted evidence.
|
|
76123
|
+
const candidates = await rawRows<{
|
|
76124
|
+
sequence: number;
|
|
76125
|
+
type: string;
|
|
76126
|
+
payload: unknown;
|
|
76127
|
+
payloadCodecVersion: number | null;
|
|
76128
|
+
}>(tx, childLifecycleEvidenceCandidatesSql(sql`${workspaceId}::uuid`, sql`${session.id}::uuid`));
|
|
76129
|
+
const noticePayload = {
|
|
76130
|
+
...input.payload,
|
|
76131
|
+
childEventEvidence: boundedChildLifecycleEvidence(candidates),
|
|
76132
|
+
};
|
|
75754
76133
|
const inserted = await tx
|
|
75755
76134
|
.insert(schema.sessionSystemUpdateOutbox)
|
|
75756
76135
|
.values(
|
|
@@ -75766,7 +76145,7 @@ async function enqueueChildLifecycleNoticeOutboxTx(
|
|
|
75766
76145
|
classification: input.classification,
|
|
75767
76146
|
sourceId: session.id,
|
|
75768
76147
|
summary: input.summary,
|
|
75769
|
-
payload:
|
|
76148
|
+
payload: noticePayload,
|
|
75770
76149
|
lineage: { ...authority.lineage, ...(input.lineage ?? {}) },
|
|
75771
76150
|
personalConnectionDelegations: authority.personalConnectionDelegations,
|
|
75772
76151
|
mcpAccountBindings: authority.mcpAccountBindings,
|