@zq-silk/yui 0.15.11 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +11 -6
- package/ARCHITECTURE.zh-CN.md +8 -4
- package/README.md +13 -5
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/artifacts/artifactCommitLock.js +16 -55
- package/dist/artifacts/taskArtifactRepository.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +92 -48
- package/dist/cli/completion.js +18 -18
- package/dist/cli/completionWizard.js +1 -1
- package/dist/cli/dynamicCompletion.js +1 -1
- package/dist/cli/interactionPolicy.js +11 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +2 -2
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1303 -1235
- package/dist/commands/agentCommands.js +4 -4
- package/dist/commands/capabilityCommands.js +1 -1
- package/dist/commands/configCommands.js +6 -57
- package/dist/commands/configOverview.js +2 -2
- package/dist/commands/durableJobCommands.js +12 -6
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +78 -29
- package/dist/commands/grantCommands.js +0 -3
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/operatorCommands.js +3 -3
- package/dist/commands/projectCommands.js +45 -25
- package/dist/commands/resourcesCommands.js +10 -40
- package/dist/commands/roleConfiguration.js +2 -6
- package/dist/commands/taskActivationCommands.js +4 -8
- package/dist/commands/taskAgentCapabilities.js +9 -0
- package/dist/commands/taskCommandSupport.js +155 -0
- package/dist/commands/taskCommandTypes.js +1 -0
- package/dist/commands/taskCommands.js +355 -732
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskExecutionCommands.js +1 -1
- package/dist/commands/taskFactCommands.js +325 -0
- package/dist/commands/taskInputCommands.js +12 -42
- package/dist/commands/taskIntegrationCommands.js +20 -58
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +12 -3
- package/dist/commands/taskPublicationVerifyCommand.js +24 -40
- package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
- package/dist/commands/taskUpstreamCommands.js +19 -8
- package/dist/commands/workflowCommands.js +6 -1
- package/dist/completion/completionInstaller.js +26 -26
- package/dist/completion/completionState.js +26 -26
- package/dist/completion/fileCompletionManager.js +6 -11
- package/dist/config/configCatalog.js +0 -2
- package/dist/config/timeZone.js +14 -0
- package/dist/config/yuiConfig.js +0 -29
- package/dist/context/runContextPack.js +5 -3
- package/dist/context/taskCatalog.js +185 -0
- package/dist/context/taskContext.js +152 -151
- package/dist/context/wakeRunReferences.js +11 -0
- package/dist/controller/agentCapabilities.js +58 -0
- package/dist/controller/agentRuntimeObserver.js +6 -11
- package/dist/controller/clientRuntime.js +22 -14
- package/dist/controller/controller.js +20 -24
- package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
- package/dist/controller/globalInputDelivery.js +13 -0
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/runtime.js +85 -43
- package/dist/controller/runtimeEventProcessor.js +0 -5
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/controller/taskAgentError.js +35 -0
- package/dist/coordination/workMailboxQueue.js +2 -24
- package/dist/core/controllerClient.js +2 -2
- package/dist/core/fileLockOwner.js +74 -0
- package/dist/decision/decision.js +17 -0
- package/dist/doctor/doctor.js +18 -9
- package/dist/event/taskEvent.js +12 -0
- package/dist/execution/workItemExecutionProjection.js +2 -2
- package/dist/executor/agentCapabilityConfig.js +43 -0
- package/dist/executor/agentConfigurationCatalog.js +49 -11
- package/dist/executor/agentExecutor.js +5 -8
- package/dist/executor/fileRoleLaunchPlanner.js +11 -53
- package/dist/executor/taskAgentCapabilities.js +64 -0
- package/dist/integration/deliveryObligation.js +1 -69
- package/dist/integration/gitIntegrationService.js +79 -90
- package/dist/integration/integrationAttempt.js +3 -14
- package/dist/integration/integrationSourceApplication.js +4 -12
- package/dist/integration/manifestTags.js +2 -2
- package/dist/job/durableJob.js +4 -25
- package/dist/job/jobAssignmentScope.js +22 -0
- package/dist/job/jobOperation.js +16 -0
- package/dist/job/jobRunner.js +2 -1
- package/dist/job/stepDirectory.js +15 -0
- package/dist/kernel/builtinCapabilities.js +15 -11
- package/dist/kernel/kernelPorts.js +1 -17
- package/dist/lifecycle/exactRunTerminalization.js +5 -10
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/message/inputControlResolution.js +3 -4
- package/dist/message/message.js +17 -20
- package/dist/message/messageContinuation.js +1 -1
- package/dist/milestone/milestone.js +11 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/observability/orchestrationMetrics.js +24 -1
- package/dist/observability/runtimeIdentity.js +1 -17
- package/dist/output/agentConfigurationPresentation.js +2 -0
- package/dist/output/timePresentation.js +1 -14
- package/dist/plugins/pluginService.js +12 -4
- package/dist/release/releaseWorkflowPorts.js +45 -96
- package/dist/release/runtimeRelease.js +9 -15
- package/dist/repository/gitWorkspace.js +371 -105
- package/dist/repository/projectMaintenanceLock.js +98 -82
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +82 -144
- package/dist/repository/taskWorkspacePreparer.js +113 -47
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/resources/autoResourceGc.js +4 -77
- package/dist/resources/liveReferences.js +7 -54
- package/dist/resources/resourceDiscovery.js +5 -118
- package/dist/resources/resourceGc.js +233 -181
- package/dist/resources/resourceRegistrar.js +5 -4
- package/dist/resources/resourceRegistry.js +3 -37
- package/dist/resources/resourceRegistryStore.js +13 -36
- package/dist/resources/sqliteResourceRegistry.js +43 -26
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewAcceptance.js +1 -1
- package/dist/review/reviewDecision.js +1 -1
- package/dist/review/reviewRound.js +8 -7
- package/dist/review/taskFinalReviewContractResolution.js +1 -1
- package/dist/runtime/acpProtocol.js +4 -51
- package/dist/runtime/acpSession.js +10 -75
- package/dist/runtime/acpSessionConfiguration.js +1 -8
- package/dist/runtime/agentEndpoint.js +3 -2
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +42 -25
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +10 -43
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +19 -7
- package/dist/scheduler/leaderWakeupProcessor.js +24 -6
- package/dist/scheduler/operatorEvent.js +12 -20
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +5 -8
- package/dist/scheduler/roleRunLiveness.js +4 -4
- package/dist/scheduler/roleRunStall.js +16 -23
- package/dist/scheduler/taskExecutionProjection.js +49 -36
- package/dist/scheduler/taskObservabilityProjection.js +6 -45
- package/dist/scheduler/taskWake.js +5 -10
- package/dist/scheduler/wakeReason.js +2 -0
- package/dist/scheduler/wakeupQueue.js +2 -4
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/contextRecords.js +106 -0
- package/dist/storage/currentTaskStore.js +1 -1
- package/dist/storage/homeLayout.js +13 -17
- package/dist/storage/migrations/agentFailureContext.js +22 -0
- package/dist/storage/migrations/currentInputContract.js +86 -0
- package/dist/storage/migrations/currentRuntimeContract.js +228 -0
- package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
- package/dist/storage/migrations/integrationContinuation.js +5 -4
- package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
- package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
- package/dist/storage/migrations/verificationPlanV1.js +162 -0
- package/dist/storage/migrations/verificationPolicy.js +74 -0
- package/dist/storage/migrations/workItemHistory.js +46 -0
- package/dist/storage/persistenceWorker.js +12 -4
- package/dist/storage/recordValidation.js +87 -0
- package/dist/storage/sqliteSchema.js +173 -1
- package/dist/storage/sqliteStore.js +172 -161
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +16 -7
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +5 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/archiveDiagnostics.js +1 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/task/remoteDeliveryService.js +89 -0
- package/dist/task/taskActivation.js +2 -25
- package/dist/task/taskActivationService.js +0 -2
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/task/taskSubmission.js +3 -14
- package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
- package/dist/telemetry/sqliteTelemetryStore.js +49 -49
- package/dist/telemetry/telemetryWiring.js +4 -3
- package/dist/tmux/tmuxManager.js +29 -20
- package/dist/verification/gateArtifact.js +15 -24
- package/dist/verification/gateArtifactStore.js +14 -7
- package/dist/verification/verificationGateService.js +29 -88
- package/dist/verification/verificationPlan.js +27 -72
- package/dist/web/assets/client/app.js +92 -17
- package/dist/web/assets/client/components.js +55 -13
- package/dist/web/assets/client/i18n.js +72 -4
- package/dist/web/assets/client/taskSurface.js +6 -5
- package/dist/web/assets/client/view.js +35 -7
- package/dist/web/assets/shell.js +6 -0
- package/dist/web/assets/styles/layout.js +7 -0
- package/dist/web/webServer.js +12 -3
- package/dist/web/webSnapshot.js +14 -85
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +110 -50
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +42 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
- package/docs/managed-turn-and-session-runtime.md +121 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/plugin-sdk.md +4 -2
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/provider-runtime.md +3 -1
- package/docs/provider-runtime.zh-CN.md +4 -2
- package/docs/release-workflow.md +190 -7
- package/docs/release-workflow.zh-CN.md +139 -5
- package/docs/roles-and-configuration.md +29 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/sqlite-control-plane-design.md +16 -3
- package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
- package/docs/task-dag-semantics.md +3 -3
- package/docs/task-dag-semantics.zh-CN.md +2 -2
- package/docs/task-delivery.md +223 -17
- package/docs/task-delivery.zh-CN.md +171 -14
- package/docs/task-discovery.md +101 -0
- package/docs/task-discovery.zh-CN.md +85 -0
- package/docs/testing/verification-levels.md +161 -9
- package/docs/testing/verification-levels.zh-CN.md +116 -9
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/execution.md +3 -2
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +24 -8
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +17 -2
- package/skills/yui-runtime/references/publication.md +26 -4
- package/skills/yui-runtime/references/recovery.md +71 -0
- package/dist/agent/agentRegistry.js +0 -10
- package/dist/agentRun/runIdentity.js +0 -22
- package/dist/commands/deliveryGuardPreflight.js +0 -30
- package/dist/commands/taskIntegrationQueueCommands.js +0 -226
- package/dist/commands/taskOverviewCommand.js +0 -376
- package/dist/completion/completionWizard.js +0 -125
- package/dist/context/dispatchContext.js +0 -110
- package/dist/context/wakeNotification.js +0 -144
- package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
- package/dist/integration/integrationQueueEntry.js +0 -191
- package/dist/integration/integrationQueueService.js +0 -810
- package/dist/release/fakeReleasePorts.js +0 -55
- package/dist/runtime/sessionOwnerRegistry.js +0 -137
- package/dist/task/deliveryGuard.js +0 -226
- /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
package/dist/task/draftPlan.js
CHANGED
|
@@ -1,25 +1,4 @@
|
|
|
1
|
-
import { RUNTIME_OBSERVATION_TASK_EVENT } from "../runtime/runtimeObservation.js";
|
|
2
1
|
import { SYSTEM_LEADER_ROLE } from "../role/systemRoles.js";
|
|
3
|
-
/**
|
|
4
|
-
* Runtime Task-event types that are emitted with a Role but no Turn in their
|
|
5
|
-
* payload. Each one is still only exempted when its `roleName` resolves to the
|
|
6
|
-
* Leader — the `runtime.` prefix is never exempted, and a type absent from this
|
|
7
|
-
* set keeps disqualifying the Draft.
|
|
8
|
-
*
|
|
9
|
-
* Turn lifecycle events are deliberately not listed: they use one generic type
|
|
10
|
-
* per transition and name the Turn in their payload, so the guard resolves that
|
|
11
|
-
* Turn's own purpose instead of trusting the type. Listing invented
|
|
12
|
-
* `turn.planning-*` types would have exempted nothing while silently
|
|
13
|
-
* disqualifying every Draft that ever held a planning conversation.
|
|
14
|
-
*/
|
|
15
|
-
export const PLANNING_ROLE_EVENT_TYPES = new Set([
|
|
16
|
-
"runtime.session-stop-requested",
|
|
17
|
-
"runtime.session-termination",
|
|
18
|
-
// Written by the liveness/host-exit paths, which admit a Draft planning Turn.
|
|
19
|
-
// Its payload carries the Role and embeds the Turn only inside the serialized
|
|
20
|
-
// observation, so it is resolved as Role-scoped rather than by that blob.
|
|
21
|
-
"runtime.process-exit-observed"
|
|
22
|
-
]);
|
|
23
2
|
export function assertDraftTaskExecutionFree(store, task) {
|
|
24
3
|
if (task.status !== "draft") {
|
|
25
4
|
throw new Error(`Task is not a Draft: ${task.id}/${task.status}.`);
|
|
@@ -168,35 +147,3 @@ function firstDraftExecutionFact(store, task) {
|
|
|
168
147
|
// of delivery authority. The domain records above own delivery facts.
|
|
169
148
|
return undefined;
|
|
170
149
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Whether one runtime/Turn Task event belongs to this Draft's own planning
|
|
173
|
-
* conversation.
|
|
174
|
-
*
|
|
175
|
-
* The binding the event carries decides this, never its type prefix:
|
|
176
|
-
*
|
|
177
|
-
* - it names a Turn → that Turn must exist on this Task and be `planning`;
|
|
178
|
-
* - it names no Turn → its exact type must be Role-scoped by construction and
|
|
179
|
-
* its `roleName` must be the Leader, the only Role a Draft may run.
|
|
180
|
-
*
|
|
181
|
-
* Everything else stays disqualifying, so an unrecognized type, a foreign Role
|
|
182
|
-
* or a Turn reference that cannot be resolved fails closed.
|
|
183
|
-
*/
|
|
184
|
-
function isDraftPlanningEvent(store, task, event) {
|
|
185
|
-
// Several emitters write an absent Turn as an empty string, so only a
|
|
186
|
-
// non-empty id counts as a reference worth resolving.
|
|
187
|
-
const runId = event.payload["runId"];
|
|
188
|
-
if (typeof runId === "string" && runId !== "") {
|
|
189
|
-
return store.getRun(task.id, runId)?.purpose === "planning";
|
|
190
|
-
}
|
|
191
|
-
// A canonical runtime observation is the event normal delivery writes for
|
|
192
|
-
// session.started/conversation.observed/turn.accepted and every terminal. It
|
|
193
|
-
// names its Turn in the same payload field as everything else and was
|
|
194
|
-
// resolved above; only an unfenced host-level observation reaches here, where
|
|
195
|
-
// the Role still has to prove it. Admitting this one type by its own binding
|
|
196
|
-
// is not the same as admitting the `runtime.` prefix.
|
|
197
|
-
if (event.type !== RUNTIME_OBSERVATION_TASK_EVENT
|
|
198
|
-
&& !PLANNING_ROLE_EVENT_TYPES.has(event.type)) {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
return event.payload["roleName"] === SYSTEM_LEADER_ROLE;
|
|
202
|
-
}
|
package/dist/task/nextAction.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { actionableExecutionLaneRecoveries } from "../execution/executionHealth.js";
|
|
2
3
|
import { governingWorkItemDeliveries, workItemDeliverySettled } from "../integration/deliveryObligation.js";
|
|
3
4
|
import { isCompletedTaskReviewEvidenceFromRuns } from "../review/reviewAcceptance.js";
|
|
4
|
-
import { actionableExecutionLaneRecoveries } from "../execution/executionHealth.js";
|
|
5
5
|
import { sameTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
|
|
6
6
|
import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractResolution.js";
|
|
7
|
-
import { draftWorkItemDependencyIssue } from "./draftPlan.js";
|
|
8
7
|
import { currentWorkItemCandidate, currentWorkItemExecutionGroup, governingWorkItemCandidate } from "../workItem/workItem.js";
|
|
8
|
+
import { draftWorkItemDependencyIssue } from "./draftPlan.js";
|
|
9
9
|
const OPEN_WORK_ITEM_STATUSES = new Set(["open"]);
|
|
10
10
|
export function projectNextAction(facts) {
|
|
11
11
|
const { task } = facts;
|
|
@@ -150,7 +150,7 @@ export function projectNextAction(facts) {
|
|
|
150
150
|
}
|
|
151
151
|
return buildAction(facts, {
|
|
152
152
|
kind: "integrate-work-item",
|
|
153
|
-
reason: `Integration ${checkingIntegration.id} awaits check-result consumption (${job?.status ?? "read current Job"});
|
|
153
|
+
reason: `Integration ${checkingIntegration.id} awaits check-result consumption (${job?.status ?? "read current Job"}); its exact Job must be consumed to finalize the attempt.`,
|
|
154
154
|
refs,
|
|
155
155
|
preconditions: [{ fact: "The Integration retains its exact check Job", satisfied: true, ref: refs[0] }],
|
|
156
156
|
recommendedCommand: `yui task integration continue ${task.id}/${checkingIntegration.id}`
|
|
@@ -378,7 +378,7 @@ export function projectNextAction(facts) {
|
|
|
378
378
|
}
|
|
379
379
|
if (deliveryWorkItems.length === 0
|
|
380
380
|
&& !taskFinalReviewRequired(facts)
|
|
381
|
-
&& !facts.reviewRounds.some((round) => (
|
|
381
|
+
&& !facts.reviewRounds.some((round) => (round.scope === "task"
|
|
382
382
|
&& (round.status === "pending" || round.status === "running")))) {
|
|
383
383
|
const reviewAlternative = facts.reviewConfig === null
|
|
384
384
|
? []
|
|
@@ -675,7 +675,6 @@ export function durableStateFingerprint(facts) {
|
|
|
675
675
|
...facts.workItems.map((item) => `work:${item.id}:${item.status}:${item.revision}:${item.updatedAt}`),
|
|
676
676
|
...facts.changeSets.map((changeSet) => `change-set:${changeSet.id}:${changeSet.headCommit}`),
|
|
677
677
|
...facts.integrations.map((attempt) => `integration:${attempt.id}:${attempt.status}:${attempt.updatedAt}`),
|
|
678
|
-
...facts.integrationQueueEntries.map((entry) => `integration-queue:${entry.id}:${entry.status}:${entry.updatedAt}`),
|
|
679
678
|
...facts.reviewRounds.map((round) => `review:${round.id}:${round.status}:${round.endedAt ?? ""}`)
|
|
680
679
|
];
|
|
681
680
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
@@ -819,22 +818,17 @@ function taskFinalReviewRole(facts) {
|
|
|
819
818
|
function ref(kind, id) {
|
|
820
819
|
return { kind, id };
|
|
821
820
|
}
|
|
822
|
-
function latestFailedReviewFor(rounds, workItemId) {
|
|
823
|
-
return [...rounds]
|
|
824
|
-
.reverse()
|
|
825
|
-
.find((round) => round.workItemId === workItemId && round.status === "failed");
|
|
826
|
-
}
|
|
827
821
|
function latestTaskFinalReview(rounds, contract) {
|
|
828
822
|
return [...rounds]
|
|
829
823
|
.reverse()
|
|
830
|
-
.find((round) => (
|
|
824
|
+
.find((round) => (round.scope === "task"
|
|
831
825
|
&& (contract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, contract))));
|
|
832
826
|
}
|
|
833
827
|
function hasValidFinalReview(facts) {
|
|
834
828
|
const contract = taskFinalReviewContract(facts);
|
|
835
829
|
const final = [...facts.reviewRounds]
|
|
836
830
|
.reverse()
|
|
837
|
-
.find((round) => (
|
|
831
|
+
.find((round) => (round.scope === "task"
|
|
838
832
|
&& (contract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, contract))));
|
|
839
833
|
if (final === undefined || !isCompletedTaskReviewEvidenceFromRuns(final, facts.reviewOutcomeEvidence?.runs ?? []))
|
|
840
834
|
return false;
|
|
@@ -853,7 +847,7 @@ function detectProtocolInconsistency(facts) {
|
|
|
853
847
|
: [ref("candidate", `${item.id}/${candidate.id}`)];
|
|
854
848
|
});
|
|
855
849
|
const reviewRefs = facts.reviewRounds
|
|
856
|
-
.filter((round) => (
|
|
850
|
+
.filter((round) => (round.scope === "task"
|
|
857
851
|
&& round.taskFinalReviewContract !== undefined))
|
|
858
852
|
.map((round) => ref("review-round", round.id));
|
|
859
853
|
return {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { contextContentDigest } from "../context/contextSnapshot.js";
|
|
2
|
+
import { publicationExternalKey } from "./publicationReference.js";
|
|
3
|
+
export const PUBLICATION_ADOPTED_EVENT = "publication.candidate-adopted";
|
|
4
|
+
/**
|
|
5
|
+
* An explicit semantic decision over fixed Git endpoints, not an ancestry
|
|
6
|
+
* heuristic or a replacement completion head. Publication metadata/verification
|
|
7
|
+
* successors may retain it only while every intervening local candidate agrees.
|
|
8
|
+
*/
|
|
9
|
+
export function publicationAdoption(publication, completion, acceptedCommit, events, publications, integrations) {
|
|
10
|
+
const unavailable = { event: null, reason: null };
|
|
11
|
+
if (completion === undefined || acceptedCommit === null)
|
|
12
|
+
return unavailable;
|
|
13
|
+
const lineage = new Set();
|
|
14
|
+
let current = publication;
|
|
15
|
+
while (current !== undefined && !lineage.has(current.id)
|
|
16
|
+
&& current.taskId === publication.taskId
|
|
17
|
+
&& current.projectId === publication.projectId
|
|
18
|
+
&& current.externalKind === publication.externalKind
|
|
19
|
+
&& publicationExternalKey(current) === publicationExternalKey(publication)
|
|
20
|
+
&& current.localCommit === publication.localCommit) {
|
|
21
|
+
lineage.add(current.id);
|
|
22
|
+
current = publications.find(p => p.id === current.supersedes);
|
|
23
|
+
}
|
|
24
|
+
const event = events.slice().reverse().find(event => {
|
|
25
|
+
const p = event.payload;
|
|
26
|
+
return event.type === PUBLICATION_ADOPTED_EVENT
|
|
27
|
+
&& event.taskId === publication.taskId
|
|
28
|
+
&& p.completionEventId === completion.id
|
|
29
|
+
&& p.acceptedCommit === acceptedCommit
|
|
30
|
+
&& p.projectId === publication.projectId
|
|
31
|
+
&& p.localCommit === publication.localCommit
|
|
32
|
+
&& lineage.has(p.publicationId ?? "");
|
|
33
|
+
});
|
|
34
|
+
if (event === undefined)
|
|
35
|
+
return unavailable;
|
|
36
|
+
const p = event.payload;
|
|
37
|
+
if (!p.acceptance?.trim()
|
|
38
|
+
|| !["user", "operator", "leader"].includes(p.by ?? "")
|
|
39
|
+
|| !/^[0-9a-f]{64}$/u.test(p.diffDigest ?? "")
|
|
40
|
+
|| !/^[0-9a-f]{40}$/u.test(p.acceptedTree ?? "")
|
|
41
|
+
|| !/^[0-9a-f]{40}$/u.test(p.candidateTree ?? "")) {
|
|
42
|
+
return { event: null, reason: `Adoption ${event.id} has incomplete fixed evidence.` };
|
|
43
|
+
}
|
|
44
|
+
if (p.integrationId === undefined)
|
|
45
|
+
return { event, reason: null };
|
|
46
|
+
const integration = integrations.find(i => i.id === p.integrationId);
|
|
47
|
+
const valid = integration !== undefined && integration.status === "committed"
|
|
48
|
+
&& integration.taskId === publication.taskId
|
|
49
|
+
&& integration.projectId === publication.projectId
|
|
50
|
+
&& integration.afterCommit === publication.localCommit
|
|
51
|
+
&& contextContentDigest(integration) === p.integrationDigest;
|
|
52
|
+
return valid ? { event, reason: null } : {
|
|
53
|
+
event: null,
|
|
54
|
+
reason: `Adoption ${event.id} references Integration ${p.integrationId}, whose committed candidate evidence is missing or changed (${integration?.status ?? "missing"}).`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -35,6 +35,9 @@ export function createPublicationReference(id, taskId, input, now) {
|
|
|
35
35
|
...(input.localCommit === undefined ? {} : {
|
|
36
36
|
localCommit: requireCommit(input.localCommit, "Publication local commit")
|
|
37
37
|
}),
|
|
38
|
+
...(input.headCommit === undefined ? {} : {
|
|
39
|
+
headCommit: requireCommit(input.headCommit, "Publication remote head")
|
|
40
|
+
}),
|
|
38
41
|
...(input.remoteCommit === undefined ? {} : {
|
|
39
42
|
remoteCommit: requireCommit(input.remoteCommit, "Publication remote commit")
|
|
40
43
|
}),
|
|
@@ -82,6 +85,13 @@ export function validatePublicationReference(reference) {
|
|
|
82
85
|
if (reference.localCommit !== undefined) {
|
|
83
86
|
requireCommit(reference.localCommit, "Publication local commit");
|
|
84
87
|
}
|
|
88
|
+
if (reference.headCommit !== undefined) {
|
|
89
|
+
requireCommit(reference.headCommit, "Publication remote head");
|
|
90
|
+
if (reference.verification === "verified"
|
|
91
|
+
&& reference.headCommit !== reference.localCommit) {
|
|
92
|
+
throw new Error("Verified publication remote head must match its local commit.");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
85
95
|
if (reference.remoteCommit !== undefined) {
|
|
86
96
|
requireCommit(reference.remoteCommit, "Publication remote commit");
|
|
87
97
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { publicationAdoption } from "./publicationAdoption.js";
|
|
1
2
|
/**
|
|
2
3
|
* One read-only delivery projection over existing Task, workspace, completion,
|
|
3
4
|
* and Publication facts. It never writes a merged flag or queries a provider.
|
|
@@ -19,13 +20,19 @@ export function projectTaskRemoteDelivery(facts) {
|
|
|
19
20
|
.filter((reference) => reference.projectId === binding.projectId);
|
|
20
21
|
const matching = expectedLocalCommit === null
|
|
21
22
|
? []
|
|
22
|
-
: projectPublications.filter((reference) => reference.localCommit === expectedLocalCommit
|
|
23
|
+
: projectPublications.filter((reference) => reference.localCommit === expectedLocalCommit
|
|
24
|
+
|| publicationAdoption(reference, expected.event, expectedLocalCommit, facts.events, facts.publications, facts.integrations ?? []).event !== null);
|
|
23
25
|
const publication = bestPublication(matching.length > 0 ? matching : projectPublications);
|
|
24
|
-
const
|
|
26
|
+
const adoptionEvidence = publication === null ? null
|
|
27
|
+
: publicationAdoption(publication, expected.event, expectedLocalCommit, facts.events, facts.publications, facts.integrations ?? []);
|
|
28
|
+
const adoption = adoptionEvidence?.event ?? null;
|
|
29
|
+
const covers = expectedLocalCommit !== null && publication !== null
|
|
30
|
+
&& (publication.localCommit === expectedLocalCommit || adoption !== null);
|
|
31
|
+
const headMatches = publication?.headCommit === undefined
|
|
32
|
+
|| publication.headCommit === publication.localCommit;
|
|
33
|
+
const coverage = projectCoverage(codeDelivery, expectedLocalCommit, publication, covers, headMatches);
|
|
25
34
|
const merged = codeDelivery === "none"
|
|
26
|
-
|| (
|
|
27
|
-
&& expectedLocalCommit !== null
|
|
28
|
-
&& publication.localCommit === expectedLocalCommit
|
|
35
|
+
|| (covers && headMatches
|
|
29
36
|
&& publication.state === "merged");
|
|
30
37
|
const verified = codeDelivery === "none"
|
|
31
38
|
|| (merged && publication?.verification === "verified");
|
|
@@ -34,6 +41,8 @@ export function projectTaskRemoteDelivery(facts) {
|
|
|
34
41
|
directory: binding.directory,
|
|
35
42
|
baseCommit,
|
|
36
43
|
expectedLocalCommit,
|
|
44
|
+
deliveryLocalCommit: covers ? publication.localCommit ?? null : null,
|
|
45
|
+
adoption,
|
|
37
46
|
codeDelivery,
|
|
38
47
|
publication,
|
|
39
48
|
state: publication?.state ?? null,
|
|
@@ -42,7 +51,8 @@ export function projectTaskRemoteDelivery(facts) {
|
|
|
42
51
|
coverage,
|
|
43
52
|
merged,
|
|
44
53
|
verified,
|
|
45
|
-
reason: projectCoverageReason(
|
|
54
|
+
reason: projectCoverageReason(expectedLocalCommit, publication, coverage, adoption) + (coverage === "stale" && adoptionEvidence?.reason
|
|
55
|
+
? ` ${adoptionEvidence.reason}` : "")
|
|
46
56
|
};
|
|
47
57
|
});
|
|
48
58
|
const codeProjects = projects.filter((project) => project.codeDelivery !== "none");
|
|
@@ -55,8 +65,9 @@ export function projectTaskRemoteDelivery(facts) {
|
|
|
55
65
|
: codeProjects.some((project) => project.coverage === "head-unavailable")
|
|
56
66
|
? "unavailable"
|
|
57
67
|
: allMerged
|
|
58
|
-
? "merged"
|
|
59
|
-
: mergedProjectCount > 0 ? "partial"
|
|
68
|
+
? allVerified ? "merged" : "unverified"
|
|
69
|
+
: mergedProjectCount > 0 ? "partial"
|
|
70
|
+
: codeProjects.some(p => p.state === "merged") ? "uncovered" : "pending";
|
|
60
71
|
return {
|
|
61
72
|
taskId: facts.task.id,
|
|
62
73
|
source: expected.source,
|
|
@@ -111,7 +122,7 @@ function expectedDelivery(facts) {
|
|
|
111
122
|
commits: new Map()
|
|
112
123
|
};
|
|
113
124
|
}
|
|
114
|
-
function completionEvent(events, completedAt) {
|
|
125
|
+
export function completionEvent(events, completedAt) {
|
|
115
126
|
const exact = latestEvent(events, "task.completed", (event) => (event.createdAt === completedAt));
|
|
116
127
|
return exact ?? latestEvent(events, "task.completed", (event) => (event.createdAt <= completedAt));
|
|
117
128
|
}
|
|
@@ -154,7 +165,7 @@ function candidateCommits(candidate) {
|
|
|
154
165
|
commit.toLowerCase()
|
|
155
166
|
]));
|
|
156
167
|
}
|
|
157
|
-
function commitMap(value) {
|
|
168
|
+
export function commitMap(value) {
|
|
158
169
|
const result = new Map();
|
|
159
170
|
for (const entry of value?.split(",") ?? []) {
|
|
160
171
|
const separator = entry.indexOf("@");
|
|
@@ -188,7 +199,7 @@ function publicationRank(reference) {
|
|
|
188
199
|
return 2;
|
|
189
200
|
return 1;
|
|
190
201
|
}
|
|
191
|
-
function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
|
|
202
|
+
function projectCoverage(codeDelivery, expectedLocalCommit, publication, covers, headMatches) {
|
|
192
203
|
if (codeDelivery === "none")
|
|
193
204
|
return "not-applicable";
|
|
194
205
|
if (expectedLocalCommit === null)
|
|
@@ -197,7 +208,9 @@ function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
|
|
|
197
208
|
return "missing";
|
|
198
209
|
if (publication.localCommit === undefined)
|
|
199
210
|
return "missing-local-commit";
|
|
200
|
-
if (
|
|
211
|
+
if (!headMatches)
|
|
212
|
+
return "head-mismatch";
|
|
213
|
+
if (!covers)
|
|
201
214
|
return "stale";
|
|
202
215
|
if (publication.state === "open")
|
|
203
216
|
return "open";
|
|
@@ -205,7 +218,7 @@ function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
|
|
|
205
218
|
return "closed";
|
|
206
219
|
return publication.verification === "verified" ? "verified" : "merged";
|
|
207
220
|
}
|
|
208
|
-
function projectCoverageReason(
|
|
221
|
+
function projectCoverageReason(expectedLocalCommit, publication, coverage, adoption) {
|
|
209
222
|
switch (coverage) {
|
|
210
223
|
case "not-applicable":
|
|
211
224
|
return "Task head equals the managed workspace base; no code delivery is required.";
|
|
@@ -216,15 +229,17 @@ function projectCoverageReason(codeDelivery, expectedLocalCommit, publication, c
|
|
|
216
229
|
case "missing-local-commit":
|
|
217
230
|
return `Publication ${publication.id} does not record a local commit.`;
|
|
218
231
|
case "stale":
|
|
219
|
-
return `Publication ${publication.id}
|
|
232
|
+
return `Publication ${publication.id} is ${publication.state}, but its local candidate ${publication.localCommit} has no valid adoption covering accepted Task head ${expectedLocalCommit}.`;
|
|
233
|
+
case "head-mismatch":
|
|
234
|
+
return `Publication ${publication.id} is ${publication.state}, but observed remote head ${publication.headCommit} differs from local candidate ${publication.localCommit}; verification is invalid.`;
|
|
220
235
|
case "open":
|
|
221
236
|
return `Publication ${publication.id} covers the Task head but is still open.`;
|
|
222
237
|
case "closed":
|
|
223
238
|
return `Publication ${publication.id} covers the Task head but is closed without merge.`;
|
|
224
239
|
case "merged":
|
|
225
|
-
return `Publication ${publication.id} reports
|
|
240
|
+
return `Publication ${publication.id} reports a covering candidate as merged, but remote verification is still required.`;
|
|
226
241
|
case "verified":
|
|
227
|
-
return `Publication ${publication.id} verifies the exact Task head as merged.`;
|
|
242
|
+
return `Publication ${publication.id} verifies ${adoption === null ? "the exact Task head" : `candidate ${publication.localCommit} adopted by ${adoption.id}`} as merged.`;
|
|
228
243
|
}
|
|
229
244
|
}
|
|
230
245
|
function compareCreated(left, right) {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { usageError } from "../errors/cliError.js";
|
|
3
|
+
import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
|
|
4
|
+
import { PUBLICATION_ADOPTED_EVENT } from "./publicationAdoption.js";
|
|
5
|
+
export function projectTaskRemoteDeliveryFromStore(store, task, currentCandidate = null) {
|
|
6
|
+
return createTaskRemoteDeliveryProof(store, task, currentCandidate).delivery;
|
|
7
|
+
}
|
|
8
|
+
export function createTaskRemoteDeliveryProof(store, task, currentCandidate = null) {
|
|
9
|
+
const publications = store.listPublicationReferences(task.id);
|
|
10
|
+
const delivery = projectTaskRemoteDelivery({
|
|
11
|
+
task,
|
|
12
|
+
events: store.listEventsByType(task.id, [
|
|
13
|
+
"task.completed", "task.archived", PUBLICATION_ADOPTED_EVENT
|
|
14
|
+
]),
|
|
15
|
+
publications,
|
|
16
|
+
managedWorkspaces: store.listManagedWorkspaces(task.id),
|
|
17
|
+
runs: task.projectBindings.length === 0 ? [] : store.listTaskRunWorkspaceBases(task.id),
|
|
18
|
+
integrations: store.listIntegrationAttempts(task.id),
|
|
19
|
+
currentCandidate
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
schemaVersion: 1,
|
|
23
|
+
task: taskRemoteDeliveryProofTask(task),
|
|
24
|
+
publications,
|
|
25
|
+
delivery
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function assertTaskRemoteDeliveryProof(store, task, proof) {
|
|
29
|
+
if (proof === undefined || proof.schemaVersion !== 1) {
|
|
30
|
+
throw usageError(`Task remote-delivery preflight proof is required: ${task.id}.`);
|
|
31
|
+
}
|
|
32
|
+
if (!isDeepStrictEqual(proof.task, taskRemoteDeliveryProofTask(task))) {
|
|
33
|
+
throw usageError(`Task changed after remote-delivery preflight: ${task.id}.`);
|
|
34
|
+
}
|
|
35
|
+
if (!isDeepStrictEqual(proof.publications, store.listPublicationReferences(task.id))) {
|
|
36
|
+
throw usageError(`Task Publication evidence changed after remote-delivery preflight: ${task.id}.`);
|
|
37
|
+
}
|
|
38
|
+
// Recheck semantic adoption/Integration facts as well as Publication versions.
|
|
39
|
+
// For provisional cancelled Tasks retain the frozen candidate used at preflight.
|
|
40
|
+
const current = projectTaskRemoteDeliveryFromStore(store, task, {
|
|
41
|
+
projects: proof.delivery.projects.flatMap(p => p.expectedLocalCommit === null
|
|
42
|
+
? [] : [{ projectId: p.projectId, commit: p.expectedLocalCommit }])
|
|
43
|
+
});
|
|
44
|
+
if (!isDeepStrictEqual(current, proof.delivery)) {
|
|
45
|
+
throw usageError(`Task delivery evidence changed after remote-delivery preflight: ${task.id}.`);
|
|
46
|
+
}
|
|
47
|
+
assertTaskRemoteDeliveryIntegrated(proof.delivery);
|
|
48
|
+
return proof.delivery;
|
|
49
|
+
}
|
|
50
|
+
export function assertTaskRemoteDeliveryIntegrated(delivery) {
|
|
51
|
+
if (!delivery.allMerged) {
|
|
52
|
+
const headUnavailable = delivery.projects.filter((project) => (project.codeDelivery !== "none"
|
|
53
|
+
&& project.coverage === "head-unavailable"));
|
|
54
|
+
if (headUnavailable.length > 0 && delivery.source === "task-completed") {
|
|
55
|
+
throw usageError(`Task ${delivery.taskId} has no frozen completion heads for: ${headUnavailable.map(({ projectId }) => projectId).join(", ")}. Historical acceptance cannot be inferred from a merge or archive.`);
|
|
56
|
+
}
|
|
57
|
+
const uncovered = delivery.projects
|
|
58
|
+
.filter((project) => project.codeDelivery !== "none" && !project.merged)
|
|
59
|
+
.map((project) => (`${project.projectId}/${project.coverage}`
|
|
60
|
+
+ (project.publication === null ? "" : `/${project.publication.id}`)))
|
|
61
|
+
.join(", ");
|
|
62
|
+
throw usageError(`Task ${delivery.taskId} is not fully merged into remote delivery: ${uncovered || "expected Project heads are unavailable"}. Inspect each Project reason with task remote-delivery. Record confirmed Publication facts; `
|
|
63
|
+
+ "for a changed post-completion candidate, review publication diff and explicitly adopt its acceptance.");
|
|
64
|
+
}
|
|
65
|
+
if (delivery.allVerified)
|
|
66
|
+
return;
|
|
67
|
+
const unverified = delivery.projects
|
|
68
|
+
.filter((project) => (project.codeDelivery !== "none"
|
|
69
|
+
&& project.merged
|
|
70
|
+
&& !project.verified))
|
|
71
|
+
.map((project) => (`${project.projectId}/${project.coverage}`
|
|
72
|
+
+ (project.publication === null ? "" : `/${project.publication.id}`)))
|
|
73
|
+
.join(", ");
|
|
74
|
+
throw usageError(`Task ${delivery.taskId} has merged Publication evidence that is not verified: `
|
|
75
|
+
+ `${unverified || "verification evidence is unavailable"}. Run task publication verify `
|
|
76
|
+
+ "for each Publication only when the provider read is authorized.");
|
|
77
|
+
}
|
|
78
|
+
export function taskRemoteDeliveryProofTask(task) {
|
|
79
|
+
return {
|
|
80
|
+
id: task.id,
|
|
81
|
+
status: task.status,
|
|
82
|
+
projectBindings: task.projectBindings,
|
|
83
|
+
...(task.workspaceIdentity === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: { workspaceIdentity: task.workspaceIdentity }),
|
|
86
|
+
...(task.completedAt === undefined ? {} : { completedAt: task.completedAt }),
|
|
87
|
+
...(task.retiredAt === undefined ? {} : { retiredAt: task.retiredAt })
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -22,7 +22,6 @@ export const TASK_ACTIVATION_EVENT = Object.freeze({
|
|
|
22
22
|
adopted: "task.activation-adopted",
|
|
23
23
|
failed: "task.activation-failed"
|
|
24
24
|
});
|
|
25
|
-
export const TASK_ACTIVATION_ORIGINS = Object.freeze(["explicit", "submit-develop"]);
|
|
26
25
|
/**
|
|
27
26
|
* The digest that decides whether a repeated requestId is the same request.
|
|
28
27
|
*
|
|
@@ -64,7 +63,6 @@ export function createTaskActivationRequest(taskId, input, now) {
|
|
|
64
63
|
partialResultRefs: []
|
|
65
64
|
},
|
|
66
65
|
startMode: input.startMode,
|
|
67
|
-
...(input.origin === undefined ? {} : { origin: input.origin }),
|
|
68
66
|
...(input.afterPlanningRun === undefined
|
|
69
67
|
? {}
|
|
70
68
|
: { afterPlanningRun: input.afterPlanningRun }),
|
|
@@ -196,26 +194,6 @@ export function admitTaskActivationRequest(task, request, planningRunIsActive) {
|
|
|
196
194
|
}
|
|
197
195
|
return { disposition: "ready", request };
|
|
198
196
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Whether the Controller may continue (auto-adopt) a released request without an
|
|
201
|
-
* explicit activation boundary call (task-32 §2.4).
|
|
202
|
-
*
|
|
203
|
-
* A deferred request already holds activation authority and is released by its
|
|
204
|
-
* planning Turn ending, so it is adoptable regardless of origin. An immediate
|
|
205
|
-
* request is auto-continued only when it carries a recognised provable origin:
|
|
206
|
-
* an explicit activation action, or a develop submission the shared transaction
|
|
207
|
-
* accepted while the Task was unplanned. A request with no origin — every one
|
|
208
|
-
* migrated from before the field existed — is deliberately held back so an
|
|
209
|
-
* upgrade cannot turn a historical pending request into a silent activation; it
|
|
210
|
-
* still adopts through the explicit `yui task activate` boundary, which does not
|
|
211
|
-
* consult origin. This replaces the old actor heuristic that both dropped legal
|
|
212
|
-
* Operator/user immediate requests and could not distinguish provenance.
|
|
213
|
-
*/
|
|
214
|
-
export function activationRequestIsControllerAdoptable(request) {
|
|
215
|
-
if (request.startMode === "after-planning-turn")
|
|
216
|
-
return true;
|
|
217
|
-
return request.origin !== undefined;
|
|
218
|
-
}
|
|
219
197
|
/**
|
|
220
198
|
* The durable terminal outcome for a requestId, read from the activation event
|
|
221
199
|
* ledger.
|
|
@@ -258,9 +236,8 @@ export function validateTaskActivationRequest(request) {
|
|
|
258
236
|
if (!["immediate", "after-planning-turn"].includes(request.startMode)) {
|
|
259
237
|
throw new Error(`Activation start mode is invalid: ${String(request.startMode)}.`);
|
|
260
238
|
}
|
|
261
|
-
if (request
|
|
262
|
-
|
|
263
|
-
throw new Error(`Activation origin is invalid: ${String(request.origin)}.`);
|
|
239
|
+
if (Object.hasOwn(request, "origin")) {
|
|
240
|
+
throw new Error("Activation contains retired origin metadata; use the storage upgrade boundary.");
|
|
264
241
|
}
|
|
265
242
|
if ((request.startMode === "after-planning-turn")
|
|
266
243
|
!== (request.afterPlanningRun !== undefined)) {
|
|
@@ -108,7 +108,6 @@ export function recordTaskActivationRequestInTransaction(tx, input, now) {
|
|
|
108
108
|
actorId: input.actorId,
|
|
109
109
|
authorityRef: input.authorityRef,
|
|
110
110
|
startMode,
|
|
111
|
-
...(input.origin === undefined ? {} : { origin: input.origin }),
|
|
112
111
|
...(planningRun === undefined ? {} : { afterPlanningRun: planningRun }),
|
|
113
112
|
environmentPlan: plan
|
|
114
113
|
}, now);
|
|
@@ -118,7 +117,6 @@ export function recordTaskActivationRequestInTransaction(tx, input, now) {
|
|
|
118
117
|
startMode: request.startMode,
|
|
119
118
|
environmentPlan: describeEnvironmentPlan(request.environmentPlan),
|
|
120
119
|
actor: request.operation.actorId,
|
|
121
|
-
...(request.origin === undefined ? {} : { origin: request.origin }),
|
|
122
120
|
...(request.afterPlanningRun === undefined
|
|
123
121
|
? {}
|
|
124
122
|
: { afterPlanningRun: request.afterPlanningRun })
|
|
@@ -131,21 +131,10 @@ export function decideSubmissionRouting(input) {
|
|
|
131
131
|
/**
|
|
132
132
|
* Normalize a possibly-absent submission intent to its effective value.
|
|
133
133
|
*
|
|
134
|
-
*
|
|
135
|
-
* every old client and every message saved before the field existed keeps its
|
|
136
|
-
* historical Leader-waking behaviour. Body text is never inspected.
|
|
137
|
-
*
|
|
138
|
-
* A legacy `--wake-policy none` carried the "save only, do not wake" meaning that
|
|
139
|
-
* `record` now names, so a caller that supplied only that older signal maps to
|
|
140
|
-
* `record` rather than silently becoming a Leader-waking `discuss`. An explicit
|
|
141
|
-
* intent always wins over the wake-policy shorthand.
|
|
134
|
+
* The current default is `discuss`. Body text is never inspected.
|
|
142
135
|
*/
|
|
143
|
-
export function normalizeSubmissionIntent(intent
|
|
144
|
-
|
|
145
|
-
return intent;
|
|
146
|
-
if (wakePolicy === "none")
|
|
147
|
-
return "record";
|
|
148
|
-
return "discuss";
|
|
136
|
+
export function normalizeSubmissionIntent(intent) {
|
|
137
|
+
return intent ?? "discuss";
|
|
149
138
|
}
|
|
150
139
|
/**
|
|
151
140
|
* Whether a retry aims at the same target the key was first bound to (§2.3). An
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Runs on the existing persistence worker's connection, never the ingress
|
|
2
|
+
* thread. Telemetry keeps its existing idempotent keys and aggregate triggers;
|
|
3
|
+
* it neither bumps Task revisions nor creates durable RPC outbox records.
|
|
4
|
+
*/
|
|
5
|
+
export function writeTelemetryBatch(db, entries, runCap) {
|
|
6
|
+
if (!Number.isSafeInteger(runCap) || runCap < 1 || entries.length > 256) {
|
|
7
|
+
throw new Error("Telemetry batch exceeds its bounded write contract.");
|
|
8
|
+
}
|
|
9
|
+
const upsert = db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id, progress_id, sequence, payload, received_at)
|
|
10
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
11
|
+
ON CONFLICT(task_id, role_name, turn_id, progress_id) DO UPDATE SET
|
|
12
|
+
sequence = excluded.sequence, payload = excluded.payload, received_at = excluded.received_at
|
|
13
|
+
WHERE excluded.received_at >= telemetry.received_at`);
|
|
14
|
+
const cap = db.prepare(`DELETE FROM telemetry WHERE task_id = ? AND turn_id = ?
|
|
15
|
+
AND (task_id, role_name, turn_id, progress_id) NOT IN (
|
|
16
|
+
SELECT task_id, role_name, turn_id, progress_id FROM telemetry
|
|
17
|
+
WHERE task_id = ? AND turn_id = ?
|
|
18
|
+
ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC LIMIT ?
|
|
19
|
+
)`);
|
|
20
|
+
db.transaction(() => {
|
|
21
|
+
const runs = new Map();
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
upsert.run(entry.taskId, entry.roleName, entry.runId, entry.progressId, entry.sequence ?? null, JSON.stringify(entry.payload), entry.receivedAt);
|
|
24
|
+
runs.set(`${entry.taskId}\0${entry.runId}`, entry);
|
|
25
|
+
}
|
|
26
|
+
for (const { taskId, runId } of runs.values())
|
|
27
|
+
cap.run(taskId, runId, taskId, runId, runCap);
|
|
28
|
+
})();
|
|
29
|
+
}
|