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