@flowdesk/core 0.1.0 → 0.1.2
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/authority-promotion.d.ts +70 -0
- package/dist/authority-promotion.d.ts.map +1 -0
- package/dist/authority-promotion.js +388 -0
- package/dist/authority-promotion.js.map +1 -0
- package/dist/chat-control-authority.d.ts +83 -0
- package/dist/chat-control-authority.d.ts.map +1 -0
- package/dist/chat-control-authority.js +238 -0
- package/dist/chat-control-authority.js.map +1 -0
- package/dist/chat-hook-authority-probe.d.ts +39 -0
- package/dist/chat-hook-authority-probe.d.ts.map +1 -0
- package/dist/chat-hook-authority-probe.js +153 -0
- package/dist/chat-hook-authority-probe.js.map +1 -0
- package/dist/chat-routing.d.ts.map +1 -1
- package/dist/chat-routing.js +18 -15
- package/dist/chat-routing.js.map +1 -1
- package/dist/connector-gateway.d.ts +34 -0
- package/dist/connector-gateway.d.ts.map +1 -0
- package/dist/connector-gateway.js +147 -0
- package/dist/connector-gateway.js.map +1 -0
- package/dist/connector-profile.d.ts +41 -0
- package/dist/connector-profile.d.ts.map +1 -0
- package/dist/connector-profile.js +125 -0
- package/dist/connector-profile.js.map +1 -0
- package/dist/controlled-conformance-doc-write.d.ts +44 -0
- package/dist/controlled-conformance-doc-write.d.ts.map +1 -0
- package/dist/controlled-conformance-doc-write.js +142 -0
- package/dist/controlled-conformance-doc-write.js.map +1 -0
- package/dist/controlled-redacted-audit-export-write.d.ts +45 -0
- package/dist/controlled-redacted-audit-export-write.d.ts.map +1 -0
- package/dist/controlled-redacted-audit-export-write.js +145 -0
- package/dist/controlled-redacted-audit-export-write.js.map +1 -0
- package/dist/core-completion-safety-contracts.d.ts +35 -0
- package/dist/core-completion-safety-contracts.d.ts.map +1 -0
- package/dist/core-completion-safety-contracts.js +98 -0
- package/dist/core-completion-safety-contracts.js.map +1 -0
- package/dist/dispatch-attempt-manifest.d.ts +59 -0
- package/dist/dispatch-attempt-manifest.d.ts.map +1 -0
- package/dist/dispatch-attempt-manifest.js +294 -0
- package/dist/dispatch-attempt-manifest.js.map +1 -0
- package/dist/dispatch-idempotency.d.ts +89 -0
- package/dist/dispatch-idempotency.d.ts.map +1 -0
- package/dist/dispatch-idempotency.js +275 -0
- package/dist/dispatch-idempotency.js.map +1 -0
- package/dist/external-auth-policy.d.ts +49 -0
- package/dist/external-auth-policy.d.ts.map +1 -0
- package/dist/external-auth-policy.js +166 -0
- package/dist/external-auth-policy.js.map +1 -0
- package/dist/fake-remote-connector-adapter.d.ts +37 -0
- package/dist/fake-remote-connector-adapter.d.ts.map +1 -0
- package/dist/fake-remote-connector-adapter.js +162 -0
- package/dist/fake-remote-connector-adapter.js.map +1 -0
- package/dist/fallback-decision.d.ts +29 -0
- package/dist/fallback-decision.d.ts.map +1 -0
- package/dist/fallback-decision.js +93 -0
- package/dist/fallback-decision.js.map +1 -0
- package/dist/fallback-regate-plan.d.ts +34 -0
- package/dist/fallback-regate-plan.d.ts.map +1 -0
- package/dist/fallback-regate-plan.js +122 -0
- package/dist/fallback-regate-plan.js.map +1 -0
- package/dist/fds1-schema-probe-result.d.ts +37 -0
- package/dist/fds1-schema-probe-result.d.ts.map +1 -0
- package/dist/fds1-schema-probe-result.js +115 -0
- package/dist/fds1-schema-probe-result.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/lane-heartbeat.d.ts +49 -0
- package/dist/lane-heartbeat.d.ts.map +1 -0
- package/dist/lane-heartbeat.js +149 -0
- package/dist/lane-heartbeat.js.map +1 -0
- package/dist/lane-lifecycle-record.d.ts +32 -0
- package/dist/lane-lifecycle-record.d.ts.map +1 -0
- package/dist/lane-lifecycle-record.js +134 -0
- package/dist/lane-lifecycle-record.js.map +1 -0
- package/dist/lane-stall-projection.d.ts +43 -0
- package/dist/lane-stall-projection.d.ts.map +1 -0
- package/dist/lane-stall-projection.js +272 -0
- package/dist/lane-stall-projection.js.map +1 -0
- package/dist/model-availability-cache.d.ts +286 -0
- package/dist/model-availability-cache.d.ts.map +1 -0
- package/dist/model-availability-cache.js +1109 -0
- package/dist/model-availability-cache.js.map +1 -0
- package/dist/operational-intelligence.d.ts +38 -0
- package/dist/operational-intelligence.d.ts.map +1 -0
- package/dist/operational-intelligence.js +107 -0
- package/dist/operational-intelligence.js.map +1 -0
- package/dist/production-approval-source.d.ts +61 -0
- package/dist/production-approval-source.d.ts.map +1 -0
- package/dist/production-approval-source.js +226 -0
- package/dist/production-approval-source.js.map +1 -0
- package/dist/production-enablement.d.ts +92 -1
- package/dist/production-enablement.d.ts.map +1 -1
- package/dist/production-enablement.js +421 -8
- package/dist/production-enablement.js.map +1 -1
- package/dist/production-verification.d.ts +31 -0
- package/dist/production-verification.d.ts.map +1 -0
- package/dist/production-verification.js +126 -0
- package/dist/production-verification.js.map +1 -0
- package/dist/provider-usage-collector.d.ts +7 -0
- package/dist/provider-usage-collector.d.ts.map +1 -1
- package/dist/provider-usage-collector.js +64 -10
- package/dist/provider-usage-collector.js.map +1 -1
- package/dist/release1-contracts.d.ts +8 -2
- package/dist/release1-contracts.d.ts.map +1 -1
- package/dist/release1-contracts.js +2 -1
- package/dist/release1-contracts.js.map +1 -1
- package/dist/remote-write-connector-gate.d.ts +91 -0
- package/dist/remote-write-connector-gate.d.ts.map +1 -0
- package/dist/remote-write-connector-gate.js +291 -0
- package/dist/remote-write-connector-gate.js.map +1 -0
- package/dist/runtime-lane-productization.d.ts +78 -0
- package/dist/runtime-lane-productization.d.ts.map +1 -0
- package/dist/runtime-lane-productization.js +270 -0
- package/dist/runtime-lane-productization.js.map +1 -0
- package/dist/sanitized-auth-capture.d.ts +50 -0
- package/dist/sanitized-auth-capture.d.ts.map +1 -0
- package/dist/sanitized-auth-capture.js +164 -0
- package/dist/sanitized-auth-capture.js.map +1 -0
- package/dist/schema-artifacts.d.ts.map +1 -1
- package/dist/schema-artifacts.js +37 -6
- package/dist/schema-artifacts.js.map +1 -1
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +21 -0
- package/dist/schema-registry.js.map +1 -1
- package/dist/session-evidence.d.ts +117 -0
- package/dist/session-evidence.d.ts.map +1 -1
- package/dist/session-evidence.js +569 -1
- package/dist/session-evidence.js.map +1 -1
- package/dist/state-paths.d.ts +1 -1
- package/dist/state-paths.d.ts.map +1 -1
- package/dist/state-paths.js +56 -6
- package/dist/state-paths.js.map +1 -1
- package/dist/status.d.ts +7 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +89 -1
- package/dist/status.js.map +1 -1
- package/dist/validators.d.ts +1 -0
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +30 -7
- package/dist/validators.js.map +1 -1
- package/package.json +1 -1
package/dist/session-evidence.js
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { dirname, resolve, sep } from "node:path";
|
|
3
|
+
import { validateFlowDeskControlledConformanceDocWriteRecordV1 } from "./controlled-conformance-doc-write.js";
|
|
4
|
+
import { validateFlowDeskControlledRedactedAuditExportWriteRecordV1 } from "./controlled-redacted-audit-export-write.js";
|
|
5
|
+
import { validateFlowDeskDispatchIdempotencySnapshotV1 } from "./dispatch-idempotency.js";
|
|
6
|
+
import { validateFlowDeskFallbackRegatePlanV1 } from "./fallback-regate-plan.js";
|
|
7
|
+
import { validateFlowDeskLaneHeartbeatRecordV1 } from "./lane-heartbeat.js";
|
|
8
|
+
import { validateFlowDeskLaneLifecycleRecordV1 } from "./lane-lifecycle-record.js";
|
|
9
|
+
import { materializeFlowDeskExactModelAvailabilityCacheFromProviderAcquisitionResultV1, planFlowDeskExactModelAvailabilityCacheRefreshV1, planFlowDeskReviewerFanoutV1, revalidateFlowDeskReviewerAssignmentsFromCacheEvidenceV1, validateFlowDeskExactModelAvailabilityCacheAcquisitionPlanV1, validateFlowDeskExactModelAvailabilityCacheProviderAcquisitionResultV1, validateFlowDeskExactModelAvailabilityCacheRefreshPlanV1, validateFlowDeskExactModelAvailabilityCacheV1, validateFlowDeskReviewerFanoutPlanV1, } from "./model-availability-cache.js";
|
|
10
|
+
import { validateFlowDeskProductionApprovalSourceV1 } from "./production-approval-source.js";
|
|
11
|
+
import { validateFlowDeskReviewerLaneConformanceObservationV1 } from "./reviewer-lane-conformance.js";
|
|
12
|
+
import { planFlowDeskRuntimeLaneLaunchV1, validateFlowDeskRuntimeLaneLaunchPlanV1, } from "./runtime-lane-productization.js";
|
|
3
13
|
import { FLOWDESK_SESSION_EVIDENCE_CLASSES, sessionEvidenceDirectoryPath, sessionEvidenceRecordPath, } from "./state-paths.js";
|
|
4
|
-
import { invalid, valid, validateNoForbiddenRawPayloads, validateOpaqueId, validateOpaqueRef, } from "./validators.js";
|
|
14
|
+
import { invalid, valid, validateNoForbiddenRawPayloads, validateOpaqueId, validateOpaqueRef, validateTopTierReviewVerdictV1, } from "./validators.js";
|
|
5
15
|
const EVIDENCE_SCHEMA_BY_CLASS = {
|
|
6
16
|
usage_authority: "flowdesk.managed_dispatch_beta.usage_authority_evidence.v1",
|
|
7
17
|
runtime_echo: "flowdesk.managed_dispatch_beta.runtime_echo_evidence.v1",
|
|
8
18
|
telemetry_correlation: "flowdesk.managed_dispatch_beta.telemetry_correlation.v1",
|
|
19
|
+
configured_verification: "flowdesk.configured_verification_result.v1",
|
|
20
|
+
sanitized_auth_capture: "flowdesk.sanitized_auth_capture_result.v1",
|
|
21
|
+
external_auth_provider_policy: "flowdesk.external_auth_provider_policy_result.v1",
|
|
22
|
+
production_approval: "flowdesk.production_approval_decision.v1",
|
|
23
|
+
production_approval_source: "flowdesk.production_approval_source.v1",
|
|
24
|
+
dispatch_idempotency: "flowdesk.dispatch_idempotency_snapshot.v1",
|
|
25
|
+
pre_dispatch_audit: "flowdesk.pre_dispatch_audit_record.v1",
|
|
26
|
+
exact_model_availability_cache: "flowdesk.exact_model_availability_cache.v1",
|
|
27
|
+
exact_model_availability_cache_refresh_plan: "flowdesk.exact_model_availability_cache_refresh_plan.v1",
|
|
28
|
+
exact_model_availability_cache_acquisition_plan: "flowdesk.exact_model_availability_cache_acquisition_plan.v1",
|
|
29
|
+
exact_model_availability_cache_provider_acquisition_result: "flowdesk.exact_model_availability_cache_provider_acquisition_result.v1",
|
|
30
|
+
reviewer_verdict: "flowdesk.top_tier_review_verdict.v1",
|
|
31
|
+
reviewer_fanout_plan: "flowdesk.reviewer_fanout_plan.v1",
|
|
32
|
+
runtime_lane_launch_plan: "flowdesk.runtime_lane_launch_plan.v1",
|
|
33
|
+
lane_lifecycle: "flowdesk.lane_lifecycle_record.v1",
|
|
34
|
+
reviewer_lane_conformance: "flowdesk.top_tier_reviewer_lane_conformance_observation.v1",
|
|
35
|
+
controlled_conformance_doc_write: "flowdesk.controlled_conformance_doc_write.v1",
|
|
36
|
+
controlled_redacted_audit_export_write: "flowdesk.controlled_redacted_audit_export_write.v1",
|
|
37
|
+
fallback_regate_plan: "flowdesk.fallback_regate_plan.v1",
|
|
38
|
+
lane_heartbeat: "flowdesk.lane_heartbeat.v1",
|
|
9
39
|
};
|
|
10
40
|
const CLASS_BY_SCHEMA = Object.fromEntries(Object.entries(EVIDENCE_SCHEMA_BY_CLASS).map(([cls, schema]) => [schema, cls]));
|
|
11
41
|
const disabledEvidenceAuthority = {
|
|
@@ -17,6 +47,426 @@ const disabledEvidenceAuthority = {
|
|
|
17
47
|
function isRecordObject(value) {
|
|
18
48
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
49
|
}
|
|
50
|
+
function exactModelCacheRefreshPlanMatchesContext(plan, input) {
|
|
51
|
+
return plan.state === "cache_hit" &&
|
|
52
|
+
plan.cache_usable_for_assignment === true &&
|
|
53
|
+
plan.expected_local_date === input.localDate &&
|
|
54
|
+
plan.expected_active_profile_ref === input.activeProfileRef &&
|
|
55
|
+
plan.expected_opencode_version_ref === input.opencodeVersionRef &&
|
|
56
|
+
plan.expected_flowdesk_package_version_ref === input.flowdeskPackageVersionRef &&
|
|
57
|
+
plan.expected_registry_hash === input.registryHash &&
|
|
58
|
+
plan.expected_policy_pack_hash === input.policyPackHash &&
|
|
59
|
+
plan.expected_auth_account_boundary_ref === input.authAccountBoundaryRef;
|
|
60
|
+
}
|
|
61
|
+
function exactModelCacheMatchesRefreshPlan(cache, plan) {
|
|
62
|
+
return plan.cache_id === cache.cache_id &&
|
|
63
|
+
plan.cache_local_date === cache.local_date &&
|
|
64
|
+
plan.cache_active_profile_ref === cache.active_profile_ref &&
|
|
65
|
+
plan.cache_opencode_version_ref === cache.opencode_version_ref &&
|
|
66
|
+
plan.cache_flowdesk_package_version_ref === cache.flowdesk_package_version_ref &&
|
|
67
|
+
plan.cache_registry_hash === cache.registry_hash &&
|
|
68
|
+
plan.cache_policy_pack_hash === cache.policy_pack_hash &&
|
|
69
|
+
plan.cache_auth_account_boundary_ref === cache.auth_account_boundary_ref;
|
|
70
|
+
}
|
|
71
|
+
export function selectFlowDeskExactModelCacheEvidencePairV1(input) {
|
|
72
|
+
const errors = [...input.reloadedEvidence.errors];
|
|
73
|
+
const blockedLabels = [];
|
|
74
|
+
if (!input.reloadedEvidence.ok)
|
|
75
|
+
blockedLabels.push("session_evidence_reload_invalid");
|
|
76
|
+
const refreshPlans = input.reloadedEvidence.entries
|
|
77
|
+
.filter((entry) => entry.evidenceClass === "exact_model_availability_cache_refresh_plan")
|
|
78
|
+
.map((entry) => entry.record)
|
|
79
|
+
.filter((record) => validateFlowDeskExactModelAvailabilityCacheRefreshPlanV1(record).ok)
|
|
80
|
+
.map((record) => record)
|
|
81
|
+
.filter((plan) => exactModelCacheRefreshPlanMatchesContext(plan, input));
|
|
82
|
+
if (refreshPlans.length === 0)
|
|
83
|
+
blockedLabels.push("cache_refresh_pair_missing");
|
|
84
|
+
if (refreshPlans.length > 1)
|
|
85
|
+
blockedLabels.push("cache_refresh_pair_ambiguous");
|
|
86
|
+
const cacheRefreshPlan = refreshPlans.length === 1 ? refreshPlans[0] : undefined;
|
|
87
|
+
const caches = cacheRefreshPlan === undefined ? [] : input.reloadedEvidence.entries
|
|
88
|
+
.filter((entry) => entry.evidenceClass === "exact_model_availability_cache")
|
|
89
|
+
.map((entry) => entry.record)
|
|
90
|
+
.filter((record) => validateFlowDeskExactModelAvailabilityCacheV1(record).ok)
|
|
91
|
+
.map((record) => record)
|
|
92
|
+
.filter((cache) => exactModelCacheMatchesRefreshPlan(cache, cacheRefreshPlan));
|
|
93
|
+
if (cacheRefreshPlan !== undefined && caches.length === 0)
|
|
94
|
+
blockedLabels.push("cache_pair_missing");
|
|
95
|
+
if (caches.length > 1)
|
|
96
|
+
blockedLabels.push("cache_pair_ambiguous");
|
|
97
|
+
const cache = caches.length === 1 ? caches[0] : undefined;
|
|
98
|
+
const ready = blockedLabels.length === 0 && cache !== undefined && cacheRefreshPlan !== undefined;
|
|
99
|
+
return {
|
|
100
|
+
ok: ready && errors.length === 0,
|
|
101
|
+
errors,
|
|
102
|
+
state: ready ? "pair_ready" : "blocked",
|
|
103
|
+
blocked_labels: [...new Set(blockedLabels)],
|
|
104
|
+
...(cache === undefined ? {} : { cache }),
|
|
105
|
+
...(cacheRefreshPlan === undefined ? {} : { cacheRefreshPlan }),
|
|
106
|
+
...disabledEvidenceAuthority,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function blockedReviewerAssignmentRevalidationFromSelection(input, selection) {
|
|
110
|
+
return {
|
|
111
|
+
schema_version: "flowdesk.reviewer_assignment_revalidation.v1",
|
|
112
|
+
ok: false,
|
|
113
|
+
errors: selection.errors,
|
|
114
|
+
state: "blocked",
|
|
115
|
+
blocked_labels: [...new Set(["cache_evidence_pair_selection_blocked", ...selection.blocked_labels])],
|
|
116
|
+
expected_local_date: input.localDate,
|
|
117
|
+
expected_active_profile_ref: input.activeProfileRef,
|
|
118
|
+
expected_opencode_version_ref: input.opencodeVersionRef,
|
|
119
|
+
expected_flowdesk_package_version_ref: input.flowdeskPackageVersionRef,
|
|
120
|
+
expected_registry_hash: input.registryHash,
|
|
121
|
+
expected_policy_pack_hash: input.policyPackHash,
|
|
122
|
+
expected_auth_account_boundary_ref: input.authAccountBoundaryRef,
|
|
123
|
+
eligible_bindings: [],
|
|
124
|
+
dispatch_authority_enabled: false,
|
|
125
|
+
providerCall: false,
|
|
126
|
+
actualLaneLaunch: false,
|
|
127
|
+
runtimeExecution: false,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function planFlowDeskReviewerFanoutFromReloadedCacheEvidenceV1(input) {
|
|
131
|
+
const selection = selectFlowDeskExactModelCacheEvidencePairV1(input);
|
|
132
|
+
const revalidation = selection.state === "pair_ready" && selection.cache !== undefined && selection.cacheRefreshPlan !== undefined
|
|
133
|
+
? revalidateFlowDeskReviewerAssignmentsFromCacheEvidenceV1({
|
|
134
|
+
cache: selection.cache,
|
|
135
|
+
cacheRefreshPlan: selection.cacheRefreshPlan,
|
|
136
|
+
localDate: input.localDate,
|
|
137
|
+
activeProfileRef: input.activeProfileRef,
|
|
138
|
+
opencodeVersionRef: input.opencodeVersionRef,
|
|
139
|
+
flowdeskPackageVersionRef: input.flowdeskPackageVersionRef,
|
|
140
|
+
registryHash: input.registryHash,
|
|
141
|
+
policyPackHash: input.policyPackHash,
|
|
142
|
+
authAccountBoundaryRef: input.authAccountBoundaryRef,
|
|
143
|
+
})
|
|
144
|
+
: blockedReviewerAssignmentRevalidationFromSelection(input, selection);
|
|
145
|
+
const fanoutPlan = planFlowDeskReviewerFanoutV1({
|
|
146
|
+
revalidation,
|
|
147
|
+
workflowId: input.workflowId,
|
|
148
|
+
attemptId: input.attemptId,
|
|
149
|
+
parentSessionRef: input.parentSessionRef,
|
|
150
|
+
agentRef: input.agentRef,
|
|
151
|
+
requestedAt: input.requestedAt,
|
|
152
|
+
requestedPerspectives: input.requestedPerspectives,
|
|
153
|
+
maxConcurrentLaneCount: input.maxConcurrentLaneCount,
|
|
154
|
+
timeoutMs: input.timeoutMs,
|
|
155
|
+
orphanMaxAgeMs: input.orphanMaxAgeMs,
|
|
156
|
+
retryBudget: input.retryBudget,
|
|
157
|
+
preLaunchAuditRef: input.preLaunchAuditRef,
|
|
158
|
+
laneLaunchApprovalRef: input.laneLaunchApprovalRef,
|
|
159
|
+
});
|
|
160
|
+
const ready = selection.state === "pair_ready" && revalidation.state === "revalidated" && fanoutPlan.state === "fanout_ready";
|
|
161
|
+
return {
|
|
162
|
+
ok: ready && selection.ok && revalidation.ok && fanoutPlan.ok,
|
|
163
|
+
errors: [...selection.errors, ...revalidation.errors, ...fanoutPlan.errors],
|
|
164
|
+
state: ready ? "fanout_ready" : "blocked",
|
|
165
|
+
blocked_labels: [...new Set([...selection.blocked_labels, ...revalidation.blocked_labels, ...fanoutPlan.blocked_labels])],
|
|
166
|
+
selection,
|
|
167
|
+
revalidation,
|
|
168
|
+
fanoutPlan,
|
|
169
|
+
dispatch_authority_enabled: false,
|
|
170
|
+
...disabledEvidenceAuthority,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function sessionEvidenceAlreadyContainsId(reloadedEvidence, evidenceClass, evidenceId) {
|
|
174
|
+
return reloadedEvidence.entries.some((entry) => entry.evidenceClass === evidenceClass && entry.evidenceId === evidenceId) ||
|
|
175
|
+
reloadedEvidence.blocked.some((entry) => entry.evidenceClass === evidenceClass && entry.evidenceId === evidenceId);
|
|
176
|
+
}
|
|
177
|
+
function providerAcquisitionResultMatchesStrictContext(result, input) {
|
|
178
|
+
return result.local_date === input.localDate &&
|
|
179
|
+
result.active_profile_ref === input.activeProfileRef &&
|
|
180
|
+
result.opencode_version_ref === input.opencodeVersionRef &&
|
|
181
|
+
result.flowdesk_package_version_ref === input.flowdeskPackageVersionRef &&
|
|
182
|
+
result.registry_hash === input.registryHash &&
|
|
183
|
+
result.policy_pack_hash === input.policyPackHash &&
|
|
184
|
+
result.auth_account_boundary_ref === input.authAccountBoundaryRef;
|
|
185
|
+
}
|
|
186
|
+
function exactModelMaterializationExpectedContext(input) {
|
|
187
|
+
return {
|
|
188
|
+
localDate: input.localDate,
|
|
189
|
+
activeProfileRef: input.activeProfileRef,
|
|
190
|
+
opencodeVersionRef: input.opencodeVersionRef,
|
|
191
|
+
flowdeskPackageVersionRef: input.flowdeskPackageVersionRef,
|
|
192
|
+
registryHash: input.registryHash,
|
|
193
|
+
policyPackHash: input.policyPackHash,
|
|
194
|
+
authAccountBoundaryRef: input.authAccountBoundaryRef,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export function materializeFlowDeskExactModelCacheEvidenceFromProviderAcquisitionEvidenceV1(input) {
|
|
198
|
+
const errors = [...input.reloadedEvidence.errors];
|
|
199
|
+
const blockedLabels = [];
|
|
200
|
+
if (!input.reloadedEvidence.ok)
|
|
201
|
+
blockedLabels.push("session_evidence_reload_invalid");
|
|
202
|
+
if (sessionEvidenceAlreadyContainsId(input.reloadedEvidence, "exact_model_availability_cache", input.targetCacheEvidenceId))
|
|
203
|
+
blockedLabels.push("target_cache_evidence_duplicate");
|
|
204
|
+
if (sessionEvidenceAlreadyContainsId(input.reloadedEvidence, "exact_model_availability_cache_refresh_plan", input.targetCacheRefreshPlanEvidenceId))
|
|
205
|
+
blockedLabels.push("target_cache_refresh_evidence_duplicate");
|
|
206
|
+
const acquisitionEntries = input.reloadedEvidence.entries
|
|
207
|
+
.filter((entry) => entry.evidenceClass === "exact_model_availability_cache_provider_acquisition_result")
|
|
208
|
+
.filter((entry) => input.providerAcquisitionEvidenceId === undefined || entry.evidenceId === input.providerAcquisitionEvidenceId)
|
|
209
|
+
.map((entry) => ({
|
|
210
|
+
...entry,
|
|
211
|
+
validation: validateFlowDeskExactModelAvailabilityCacheProviderAcquisitionResultV1(entry.record),
|
|
212
|
+
}))
|
|
213
|
+
.filter((entry) => entry.validation.ok)
|
|
214
|
+
.map((entry) => ({
|
|
215
|
+
...entry,
|
|
216
|
+
record: entry.record,
|
|
217
|
+
}))
|
|
218
|
+
.filter((entry) => input.providerAcquisitionEvidenceId !== undefined || providerAcquisitionResultMatchesStrictContext(entry.record, input));
|
|
219
|
+
if (acquisitionEntries.length === 0)
|
|
220
|
+
blockedLabels.push("provider_acquisition_evidence_missing");
|
|
221
|
+
if (acquisitionEntries.length > 1)
|
|
222
|
+
blockedLabels.push("provider_acquisition_evidence_ambiguous");
|
|
223
|
+
const acquisition = acquisitionEntries.length === 1 ? acquisitionEntries[0].record : undefined;
|
|
224
|
+
const materialized = materializeFlowDeskExactModelAvailabilityCacheFromProviderAcquisitionResultV1({
|
|
225
|
+
providerAcquisitionResult: acquisition,
|
|
226
|
+
cacheId: input.cacheId,
|
|
227
|
+
entryId: input.entryId,
|
|
228
|
+
expectedContext: exactModelMaterializationExpectedContext(input),
|
|
229
|
+
});
|
|
230
|
+
if (!materialized.ok) {
|
|
231
|
+
errors.push(...materialized.errors);
|
|
232
|
+
blockedLabels.push(...materialized.blocked_labels);
|
|
233
|
+
}
|
|
234
|
+
const cache = materialized.cache;
|
|
235
|
+
const cacheRefreshPlan = cache === undefined ? undefined : planFlowDeskExactModelAvailabilityCacheRefreshV1({
|
|
236
|
+
cache,
|
|
237
|
+
localDate: input.localDate,
|
|
238
|
+
activeProfileRef: input.activeProfileRef,
|
|
239
|
+
opencodeVersionRef: input.opencodeVersionRef,
|
|
240
|
+
flowdeskPackageVersionRef: input.flowdeskPackageVersionRef,
|
|
241
|
+
registryHash: input.registryHash,
|
|
242
|
+
policyPackHash: input.policyPackHash,
|
|
243
|
+
authAccountBoundaryRef: input.authAccountBoundaryRef,
|
|
244
|
+
});
|
|
245
|
+
if (cacheRefreshPlan !== undefined) {
|
|
246
|
+
const refreshValidation = validateFlowDeskExactModelAvailabilityCacheRefreshPlanV1(cacheRefreshPlan);
|
|
247
|
+
if (!refreshValidation.ok) {
|
|
248
|
+
errors.push(...refreshValidation.errors.map((error) => `cache_refresh_plan: ${error}`));
|
|
249
|
+
blockedLabels.push("materialized_cache_refresh_plan_invalid");
|
|
250
|
+
}
|
|
251
|
+
if (cacheRefreshPlan.state !== "cache_hit")
|
|
252
|
+
blockedLabels.push("materialized_cache_refresh_not_cache_hit");
|
|
253
|
+
}
|
|
254
|
+
if (cache !== undefined) {
|
|
255
|
+
const existingRefreshPlans = input.reloadedEvidence.entries
|
|
256
|
+
.filter((entry) => entry.evidenceClass === "exact_model_availability_cache_refresh_plan")
|
|
257
|
+
.map((entry) => entry.record)
|
|
258
|
+
.filter((record) => validateFlowDeskExactModelAvailabilityCacheRefreshPlanV1(record).ok)
|
|
259
|
+
.map((record) => record)
|
|
260
|
+
.filter((plan) => exactModelCacheRefreshPlanMatchesContext(plan, input));
|
|
261
|
+
if (existingRefreshPlans.length > 0)
|
|
262
|
+
blockedLabels.push("cache_refresh_context_already_exists");
|
|
263
|
+
const existingCachesForPlannedRefresh = cacheRefreshPlan === undefined ? [] : input.reloadedEvidence.entries
|
|
264
|
+
.filter((entry) => entry.evidenceClass === "exact_model_availability_cache")
|
|
265
|
+
.map((entry) => entry.record)
|
|
266
|
+
.filter((record) => validateFlowDeskExactModelAvailabilityCacheV1(record).ok)
|
|
267
|
+
.map((record) => record)
|
|
268
|
+
.filter((existingCache) => exactModelCacheMatchesRefreshPlan(existingCache, cacheRefreshPlan));
|
|
269
|
+
if (existingCachesForPlannedRefresh.length > 0)
|
|
270
|
+
blockedLabels.push("cache_context_already_exists");
|
|
271
|
+
}
|
|
272
|
+
if (blockedLabels.length > 0 || cache === undefined || cacheRefreshPlan === undefined)
|
|
273
|
+
return {
|
|
274
|
+
ok: false,
|
|
275
|
+
errors,
|
|
276
|
+
state: "blocked",
|
|
277
|
+
blocked_labels: [...new Set(blockedLabels)],
|
|
278
|
+
...(cache === undefined ? {} : { cache }),
|
|
279
|
+
...(cacheRefreshPlan === undefined ? {} : { cacheRefreshPlan }),
|
|
280
|
+
writeIntents: [],
|
|
281
|
+
...disabledEvidenceAuthority,
|
|
282
|
+
};
|
|
283
|
+
const cacheIntent = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
284
|
+
workflowId: input.workflowId,
|
|
285
|
+
evidenceId: input.targetCacheEvidenceId,
|
|
286
|
+
record: cache,
|
|
287
|
+
});
|
|
288
|
+
const refreshIntent = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
289
|
+
workflowId: input.workflowId,
|
|
290
|
+
evidenceId: input.targetCacheRefreshPlanEvidenceId,
|
|
291
|
+
record: cacheRefreshPlan,
|
|
292
|
+
});
|
|
293
|
+
if (!cacheIntent.ok || cacheIntent.writeIntent === undefined || !refreshIntent.ok || refreshIntent.writeIntent === undefined)
|
|
294
|
+
return {
|
|
295
|
+
ok: false,
|
|
296
|
+
errors: [...errors, ...cacheIntent.errors, ...refreshIntent.errors],
|
|
297
|
+
state: "blocked",
|
|
298
|
+
blocked_labels: [...new Set([...blockedLabels, "cache_materialization_write_intent_invalid"])],
|
|
299
|
+
cache,
|
|
300
|
+
cacheRefreshPlan,
|
|
301
|
+
writeIntents: [],
|
|
302
|
+
...disabledEvidenceAuthority,
|
|
303
|
+
};
|
|
304
|
+
const writeIntents = [cacheIntent.writeIntent, refreshIntent.writeIntent];
|
|
305
|
+
if (input.rootDir === undefined)
|
|
306
|
+
return {
|
|
307
|
+
ok: true,
|
|
308
|
+
errors,
|
|
309
|
+
state: "materialized",
|
|
310
|
+
blocked_labels: [],
|
|
311
|
+
cache,
|
|
312
|
+
cacheRefreshPlan,
|
|
313
|
+
writeIntents,
|
|
314
|
+
...disabledEvidenceAuthority,
|
|
315
|
+
};
|
|
316
|
+
const applyResult = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, writeIntents);
|
|
317
|
+
if (!applyResult.ok)
|
|
318
|
+
return {
|
|
319
|
+
ok: false,
|
|
320
|
+
errors: [...errors, ...applyResult.errors],
|
|
321
|
+
state: "blocked",
|
|
322
|
+
blocked_labels: [...new Set([...blockedLabels, "cache_materialization_apply_failed"])],
|
|
323
|
+
cache,
|
|
324
|
+
cacheRefreshPlan,
|
|
325
|
+
writeIntents,
|
|
326
|
+
applyResult,
|
|
327
|
+
...disabledEvidenceAuthority,
|
|
328
|
+
};
|
|
329
|
+
const reloadedEvidence = reloadFlowDeskSessionEvidenceV1({ workflowId: input.workflowId, rootDir: input.rootDir });
|
|
330
|
+
const selection = selectFlowDeskExactModelCacheEvidencePairV1({
|
|
331
|
+
reloadedEvidence,
|
|
332
|
+
localDate: input.localDate,
|
|
333
|
+
activeProfileRef: input.activeProfileRef,
|
|
334
|
+
opencodeVersionRef: input.opencodeVersionRef,
|
|
335
|
+
flowdeskPackageVersionRef: input.flowdeskPackageVersionRef,
|
|
336
|
+
registryHash: input.registryHash,
|
|
337
|
+
policyPackHash: input.policyPackHash,
|
|
338
|
+
authAccountBoundaryRef: input.authAccountBoundaryRef,
|
|
339
|
+
});
|
|
340
|
+
const ready = reloadedEvidence.ok && selection.state === "pair_ready";
|
|
341
|
+
return {
|
|
342
|
+
ok: ready,
|
|
343
|
+
errors: [...errors, ...reloadedEvidence.errors, ...selection.errors],
|
|
344
|
+
state: ready ? "materialized" : "blocked",
|
|
345
|
+
blocked_labels: ready ? [] : [...new Set([...blockedLabels, "cache_materialization_reload_verification_failed", ...selection.blocked_labels])],
|
|
346
|
+
cache,
|
|
347
|
+
cacheRefreshPlan,
|
|
348
|
+
writeIntents,
|
|
349
|
+
applyResult,
|
|
350
|
+
reloadedEvidence,
|
|
351
|
+
selection,
|
|
352
|
+
...disabledEvidenceAuthority,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
export function materializeFlowDeskRuntimeLaneLaunchPlansFromReviewerFanoutEvidenceV1(input) {
|
|
356
|
+
const errors = [...input.reloadedEvidence.errors];
|
|
357
|
+
const blockedLabels = [];
|
|
358
|
+
if (!input.reloadedEvidence.ok)
|
|
359
|
+
blockedLabels.push("session_evidence_reload_invalid");
|
|
360
|
+
const targetIds = input.targetLaunchPlanEvidenceIds;
|
|
361
|
+
if (targetIds.length === 0)
|
|
362
|
+
blockedLabels.push("target_launch_plan_ids_missing");
|
|
363
|
+
if (new Set(targetIds).size !== targetIds.length)
|
|
364
|
+
blockedLabels.push("target_launch_plan_ids_duplicate");
|
|
365
|
+
for (const targetId of targetIds) {
|
|
366
|
+
if (sessionEvidenceAlreadyContainsId(input.reloadedEvidence, "runtime_lane_launch_plan", targetId))
|
|
367
|
+
blockedLabels.push("target_runtime_launch_plan_evidence_duplicate");
|
|
368
|
+
}
|
|
369
|
+
const fanoutEntries = input.reloadedEvidence.entries
|
|
370
|
+
.filter((entry) => entry.evidenceClass === "reviewer_fanout_plan")
|
|
371
|
+
.filter((entry) => input.reviewerFanoutEvidenceId === undefined || entry.evidenceId === input.reviewerFanoutEvidenceId)
|
|
372
|
+
.map((entry) => ({ ...entry, validation: validateFlowDeskReviewerFanoutPlanV1(entry.record) }))
|
|
373
|
+
.filter((entry) => entry.validation.ok)
|
|
374
|
+
.map((entry) => ({ ...entry, record: entry.record }))
|
|
375
|
+
.filter((entry) => entry.record.workflow_id === input.workflowId);
|
|
376
|
+
if (fanoutEntries.length === 0)
|
|
377
|
+
blockedLabels.push("reviewer_fanout_evidence_missing");
|
|
378
|
+
if (fanoutEntries.length > 1)
|
|
379
|
+
blockedLabels.push("reviewer_fanout_evidence_ambiguous");
|
|
380
|
+
const fanoutPlan = fanoutEntries.length === 1 ? fanoutEntries[0].record : undefined;
|
|
381
|
+
if (fanoutPlan !== undefined && (fanoutPlan.state !== "fanout_ready" || !fanoutPlan.ok))
|
|
382
|
+
blockedLabels.push("reviewer_fanout_not_ready");
|
|
383
|
+
const launchRequests = fanoutPlan?.runtime_lane_launch_requests ?? [];
|
|
384
|
+
if (fanoutPlan !== undefined && targetIds.length !== launchRequests.length)
|
|
385
|
+
blockedLabels.push("target_launch_plan_count_mismatch");
|
|
386
|
+
const launchPlans = launchRequests.map((request) => planFlowDeskRuntimeLaneLaunchV1({
|
|
387
|
+
request,
|
|
388
|
+
sdkClientAvailable: input.sdkClientAvailable,
|
|
389
|
+
durableEvidenceRootRef: input.durableEvidenceRootRef,
|
|
390
|
+
}));
|
|
391
|
+
for (const [index, plan] of launchPlans.entries()) {
|
|
392
|
+
errors.push(...plan.errors.map((error) => `launch_plans[${index}]: ${error}`));
|
|
393
|
+
if (plan.state !== "launch_ready")
|
|
394
|
+
blockedLabels.push(...(plan.blocked_labels.length > 0 ? plan.blocked_labels : ["runtime_launch_plan_blocked"]));
|
|
395
|
+
}
|
|
396
|
+
if (blockedLabels.length > 0 || fanoutPlan === undefined || launchPlans.length === 0)
|
|
397
|
+
return {
|
|
398
|
+
ok: false,
|
|
399
|
+
errors,
|
|
400
|
+
state: "blocked",
|
|
401
|
+
blocked_labels: [...new Set(blockedLabels)],
|
|
402
|
+
...(fanoutPlan === undefined ? {} : { fanoutPlan }),
|
|
403
|
+
launchPlans,
|
|
404
|
+
writeIntents: [],
|
|
405
|
+
...disabledEvidenceAuthority,
|
|
406
|
+
};
|
|
407
|
+
const prepared = launchPlans.map((plan, index) => prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
408
|
+
workflowId: input.workflowId,
|
|
409
|
+
evidenceId: targetIds[index],
|
|
410
|
+
record: plan,
|
|
411
|
+
}));
|
|
412
|
+
const prepareErrors = prepared.flatMap((result) => result.errors);
|
|
413
|
+
const writeIntents = prepared
|
|
414
|
+
.map((result) => result.writeIntent)
|
|
415
|
+
.filter((intent) => intent !== undefined);
|
|
416
|
+
if (prepareErrors.length > 0 || writeIntents.length !== launchPlans.length)
|
|
417
|
+
return {
|
|
418
|
+
ok: false,
|
|
419
|
+
errors: [...errors, ...prepareErrors],
|
|
420
|
+
state: "blocked",
|
|
421
|
+
blocked_labels: ["runtime_launch_plan_write_intent_invalid"],
|
|
422
|
+
fanoutPlan,
|
|
423
|
+
launchPlans,
|
|
424
|
+
writeIntents: [],
|
|
425
|
+
...disabledEvidenceAuthority,
|
|
426
|
+
};
|
|
427
|
+
if (input.rootDir === undefined)
|
|
428
|
+
return {
|
|
429
|
+
ok: true,
|
|
430
|
+
errors,
|
|
431
|
+
state: "materialized",
|
|
432
|
+
blocked_labels: [],
|
|
433
|
+
fanoutPlan,
|
|
434
|
+
launchPlans,
|
|
435
|
+
writeIntents,
|
|
436
|
+
...disabledEvidenceAuthority,
|
|
437
|
+
};
|
|
438
|
+
const applyResult = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, writeIntents);
|
|
439
|
+
if (!applyResult.ok)
|
|
440
|
+
return {
|
|
441
|
+
ok: false,
|
|
442
|
+
errors: [...errors, ...applyResult.errors],
|
|
443
|
+
state: "blocked",
|
|
444
|
+
blocked_labels: ["runtime_launch_plan_apply_failed"],
|
|
445
|
+
fanoutPlan,
|
|
446
|
+
launchPlans,
|
|
447
|
+
writeIntents,
|
|
448
|
+
applyResult,
|
|
449
|
+
...disabledEvidenceAuthority,
|
|
450
|
+
};
|
|
451
|
+
const reloadedEvidence = reloadFlowDeskSessionEvidenceV1({ workflowId: input.workflowId, rootDir: input.rootDir });
|
|
452
|
+
const reloadedIds = new Set(reloadedEvidence.entries
|
|
453
|
+
.filter((entry) => entry.evidenceClass === "runtime_lane_launch_plan")
|
|
454
|
+
.map((entry) => entry.evidenceId));
|
|
455
|
+
const allReloaded = targetIds.every((targetId) => reloadedIds.has(targetId));
|
|
456
|
+
const ready = reloadedEvidence.ok && allReloaded;
|
|
457
|
+
return {
|
|
458
|
+
ok: ready,
|
|
459
|
+
errors: [...errors, ...reloadedEvidence.errors],
|
|
460
|
+
state: ready ? "materialized" : "blocked",
|
|
461
|
+
blocked_labels: ready ? [] : ["runtime_launch_plan_reload_verification_failed"],
|
|
462
|
+
fanoutPlan,
|
|
463
|
+
launchPlans,
|
|
464
|
+
writeIntents,
|
|
465
|
+
applyResult,
|
|
466
|
+
reloadedEvidence,
|
|
467
|
+
...disabledEvidenceAuthority,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
20
470
|
function validateSchemaVersionForClass(record, evidenceClass) {
|
|
21
471
|
const expected = EVIDENCE_SCHEMA_BY_CLASS[evidenceClass];
|
|
22
472
|
return record.schema_version === expected
|
|
@@ -27,12 +477,68 @@ function validateEvidenceShape(record, evidenceClass) {
|
|
|
27
477
|
const schemaCheck = validateSchemaVersionForClass(record, evidenceClass);
|
|
28
478
|
if (!schemaCheck.ok)
|
|
29
479
|
return schemaCheck;
|
|
480
|
+
if (evidenceClass === "dispatch_idempotency")
|
|
481
|
+
return validateFlowDeskDispatchIdempotencySnapshotV1(record);
|
|
482
|
+
if (evidenceClass === "production_approval_source")
|
|
483
|
+
return validateFlowDeskProductionApprovalSourceV1(record);
|
|
484
|
+
if (evidenceClass === "reviewer_verdict")
|
|
485
|
+
return validateTopTierReviewVerdictV1(record);
|
|
486
|
+
if (evidenceClass === "exact_model_availability_cache")
|
|
487
|
+
return validateFlowDeskExactModelAvailabilityCacheV1(record);
|
|
488
|
+
if (evidenceClass === "exact_model_availability_cache_refresh_plan")
|
|
489
|
+
return validateFlowDeskExactModelAvailabilityCacheRefreshPlanV1(record);
|
|
490
|
+
if (evidenceClass === "exact_model_availability_cache_acquisition_plan")
|
|
491
|
+
return validateFlowDeskExactModelAvailabilityCacheAcquisitionPlanV1(record);
|
|
492
|
+
if (evidenceClass === "exact_model_availability_cache_provider_acquisition_result")
|
|
493
|
+
return validateFlowDeskExactModelAvailabilityCacheProviderAcquisitionResultV1(record);
|
|
494
|
+
if (evidenceClass === "reviewer_fanout_plan")
|
|
495
|
+
return validateFlowDeskReviewerFanoutPlanV1(record);
|
|
496
|
+
if (evidenceClass === "runtime_lane_launch_plan")
|
|
497
|
+
return validateFlowDeskRuntimeLaneLaunchPlanV1(record);
|
|
498
|
+
if (evidenceClass === "lane_lifecycle")
|
|
499
|
+
return validateFlowDeskLaneLifecycleRecordV1(record);
|
|
500
|
+
if (evidenceClass === "lane_heartbeat")
|
|
501
|
+
return validateFlowDeskLaneHeartbeatRecordV1(record);
|
|
502
|
+
if (evidenceClass === "reviewer_lane_conformance")
|
|
503
|
+
return validateFlowDeskReviewerLaneConformanceObservationV1(record);
|
|
504
|
+
if (evidenceClass === "controlled_conformance_doc_write")
|
|
505
|
+
return validateFlowDeskControlledConformanceDocWriteRecordV1(record);
|
|
506
|
+
if (evidenceClass === "controlled_redacted_audit_export_write")
|
|
507
|
+
return validateFlowDeskControlledRedactedAuditExportWriteRecordV1(record);
|
|
508
|
+
if (evidenceClass === "fallback_regate_plan")
|
|
509
|
+
return validateFlowDeskFallbackRegatePlanV1(record);
|
|
30
510
|
const requiredCommon = ["schema_version"];
|
|
31
511
|
for (const key of requiredCommon)
|
|
32
512
|
if (!(key in record))
|
|
33
513
|
return invalid(`evidence is missing required field: ${key}`);
|
|
34
514
|
return validateNoForbiddenRawPayloads(record, "session_evidence_record");
|
|
35
515
|
}
|
|
516
|
+
function validateOptionalTimestampFreshness(record, rejectStaleAt) {
|
|
517
|
+
if (rejectStaleAt === undefined || !("expires_at" in record))
|
|
518
|
+
return valid();
|
|
519
|
+
const checkedAt = Date.parse(rejectStaleAt);
|
|
520
|
+
if (!Number.isFinite(checkedAt))
|
|
521
|
+
return invalid("rejectStaleAt must be a parseable timestamp");
|
|
522
|
+
if (typeof record.expires_at !== "string")
|
|
523
|
+
return invalid("evidence expires_at must be a timestamp string");
|
|
524
|
+
const expiresAt = Date.parse(record.expires_at);
|
|
525
|
+
if (!Number.isFinite(expiresAt))
|
|
526
|
+
return invalid("evidence expires_at must be parseable");
|
|
527
|
+
return expiresAt > checkedAt ? valid() : invalid("evidence is stale");
|
|
528
|
+
}
|
|
529
|
+
function validateOptionalProfileAlignment(record, expectedProfileRef) {
|
|
530
|
+
if (expectedProfileRef === undefined)
|
|
531
|
+
return valid();
|
|
532
|
+
const expected = validateOpaqueRef(expectedProfileRef, "expected_profile_ref");
|
|
533
|
+
if (!expected.ok)
|
|
534
|
+
return expected;
|
|
535
|
+
const profileRef = record.profile_ref ?? record.auth_profile_ref;
|
|
536
|
+
if (profileRef === undefined)
|
|
537
|
+
return valid();
|
|
538
|
+
return profileRef === expectedProfileRef
|
|
539
|
+
? valid()
|
|
540
|
+
: invalid("evidence profile_ref mismatch");
|
|
541
|
+
}
|
|
36
542
|
function ensureWorkflowAlignment(record, workflowId) {
|
|
37
543
|
if (!("workflow_id" in record))
|
|
38
544
|
return valid();
|
|
@@ -147,6 +653,8 @@ export function applyFlowDeskSessionEvidenceWriteIntentsV1(rootDir, intents) {
|
|
|
147
653
|
const writtenPaths = [];
|
|
148
654
|
const root = resolve(rootDir);
|
|
149
655
|
try {
|
|
656
|
+
const seenTargets = new Set();
|
|
657
|
+
const seenTemps = new Set();
|
|
150
658
|
for (const intent of intents) {
|
|
151
659
|
const intentResult = validateSessionEvidenceWriteIntent(intent);
|
|
152
660
|
if (!intentResult.ok)
|
|
@@ -158,6 +666,20 @@ export function applyFlowDeskSessionEvidenceWriteIntentsV1(rootDir, intents) {
|
|
|
158
666
|
};
|
|
159
667
|
const target = safeJoin(root, intent.path);
|
|
160
668
|
const temp = safeJoin(root, intent.tempPath);
|
|
669
|
+
if (seenTargets.has(target))
|
|
670
|
+
return {
|
|
671
|
+
...invalid("session evidence batch contains duplicate target path"),
|
|
672
|
+
rootDir: root,
|
|
673
|
+
writtenPaths,
|
|
674
|
+
...disabledEvidenceAuthority,
|
|
675
|
+
};
|
|
676
|
+
if (seenTemps.has(temp))
|
|
677
|
+
return {
|
|
678
|
+
...invalid("session evidence batch contains duplicate temp path"),
|
|
679
|
+
rootDir: root,
|
|
680
|
+
writtenPaths,
|
|
681
|
+
...disabledEvidenceAuthority,
|
|
682
|
+
};
|
|
161
683
|
if (dirname(target) !== dirname(temp))
|
|
162
684
|
return {
|
|
163
685
|
...invalid("session evidence tempPath must stay beside target path"),
|
|
@@ -165,6 +687,12 @@ export function applyFlowDeskSessionEvidenceWriteIntentsV1(rootDir, intents) {
|
|
|
165
687
|
writtenPaths,
|
|
166
688
|
...disabledEvidenceAuthority,
|
|
167
689
|
};
|
|
690
|
+
seenTargets.add(target);
|
|
691
|
+
seenTemps.add(temp);
|
|
692
|
+
}
|
|
693
|
+
for (const intent of intents) {
|
|
694
|
+
const target = safeJoin(root, intent.path);
|
|
695
|
+
const temp = safeJoin(root, intent.tempPath);
|
|
168
696
|
mkdirSync(dirname(target), { recursive: true });
|
|
169
697
|
writeFileSync(temp, JSON.stringify(intent.record), "utf8");
|
|
170
698
|
renameSync(temp, target);
|
|
@@ -316,6 +844,26 @@ export function reloadFlowDeskSessionEvidenceV1(options) {
|
|
|
316
844
|
});
|
|
317
845
|
continue;
|
|
318
846
|
}
|
|
847
|
+
const profileAlignment = validateOptionalProfileAlignment(parsed, options.expectedProfileRef);
|
|
848
|
+
if (!profileAlignment.ok) {
|
|
849
|
+
blocked.push({
|
|
850
|
+
evidenceClass,
|
|
851
|
+
evidenceId,
|
|
852
|
+
reason: profileAlignment.errors.join("; "),
|
|
853
|
+
path: expectedRelative,
|
|
854
|
+
});
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const freshness = validateOptionalTimestampFreshness(parsed, options.rejectStaleAt);
|
|
858
|
+
if (!freshness.ok) {
|
|
859
|
+
blocked.push({
|
|
860
|
+
evidenceClass,
|
|
861
|
+
evidenceId,
|
|
862
|
+
reason: freshness.errors.join("; "),
|
|
863
|
+
path: expectedRelative,
|
|
864
|
+
});
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
319
867
|
entries.push({
|
|
320
868
|
evidenceClass,
|
|
321
869
|
evidenceId,
|
|
@@ -333,4 +881,24 @@ export function reloadFlowDeskSessionEvidenceV1(options) {
|
|
|
333
881
|
};
|
|
334
882
|
return reloadResult;
|
|
335
883
|
}
|
|
884
|
+
export function summarizeFlowDeskSessionEvidenceInventoryV1(workflowId, reload) {
|
|
885
|
+
return {
|
|
886
|
+
schema_version: "flowdesk.session_evidence_inventory.v1",
|
|
887
|
+
workflow_id: workflowId,
|
|
888
|
+
total_entries: reload.entries.length,
|
|
889
|
+
total_blocked: reload.blocked.length,
|
|
890
|
+
classes: FLOWDESK_SESSION_EVIDENCE_CLASSES.map((evidenceClass) => {
|
|
891
|
+
const blocked = reload.blocked.filter((entry) => entry.evidenceClass === evidenceClass);
|
|
892
|
+
return {
|
|
893
|
+
evidenceClass,
|
|
894
|
+
valid: reload.entries.filter((entry) => entry.evidenceClass === evidenceClass).length,
|
|
895
|
+
blocked: blocked.length,
|
|
896
|
+
...(blocked.length === 0
|
|
897
|
+
? {}
|
|
898
|
+
: { lastBlockedReason: blocked[blocked.length - 1].reason }),
|
|
899
|
+
};
|
|
900
|
+
}),
|
|
901
|
+
...disabledEvidenceAuthority,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
336
904
|
//# sourceMappingURL=session-evidence.js.map
|