@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18
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/README.md +134 -15
- package/assets/integrations/skills/pappra/SKILL.md +28 -7
- package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
- package/assets/integrations/skills/pappra/references/environment.md +360 -0
- package/assets/integrations/skills/pappra/references/hosts.md +20 -0
- package/assets/integrations/skills/pappra/references/operations.md +36 -5
- package/dist/adapters/pi/recovery-extension.d.ts +65 -0
- package/dist/adapters/pi/recovery-extension.js +238 -0
- package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
- package/dist/adapters/pi/session-transition-extension.js +265 -0
- package/dist/app/automatic-handoff-events.d.ts +26 -0
- package/dist/app/automatic-handoff-events.js +134 -0
- package/dist/app/bounded-file.d.ts +2 -0
- package/dist/app/bounded-file.js +2 -0
- package/dist/app/capability-evidence/claude.d.ts +37 -0
- package/dist/app/capability-evidence/claude.js +206 -0
- package/dist/app/capability-evidence/codex.d.ts +35 -0
- package/dist/app/capability-evidence/codex.js +193 -0
- package/dist/app/capability-evidence/index.d.ts +33 -0
- package/dist/app/capability-evidence/index.js +117 -0
- package/dist/app/capability-evidence/pi.d.ts +73 -0
- package/dist/app/capability-evidence/pi.js +669 -0
- package/dist/app/capability-evidence/shared.d.ts +69 -0
- package/dist/app/capability-evidence/shared.js +195 -0
- package/dist/app/collab-auto-handoff.d.ts +55 -0
- package/dist/app/collab-auto-handoff.js +384 -0
- package/dist/app/collab-bindings.d.ts +57 -0
- package/dist/app/collab-bindings.js +1006 -0
- package/dist/app/collab-bootstrap-transport.d.ts +163 -0
- package/dist/app/collab-bootstrap-transport.js +769 -0
- package/dist/app/collab-bootstrap.d.ts +137 -0
- package/dist/app/collab-bootstrap.js +3455 -0
- package/dist/app/collab-handoff.d.ts +12 -0
- package/dist/app/collab-handoff.js +511 -0
- package/dist/app/collab-messages.d.ts +2 -0
- package/dist/app/collab-messages.js +2 -0
- package/dist/app/collab-notify.d.ts +156 -0
- package/dist/app/collab-notify.js +1208 -0
- package/dist/app/collab-preflight.d.ts +34 -0
- package/dist/app/collab-preflight.js +96 -0
- package/dist/app/collab-recovery-executor.d.ts +161 -0
- package/dist/app/collab-recovery-executor.js +1153 -0
- package/dist/app/collab-recovery-projection.d.ts +103 -0
- package/dist/app/collab-recovery-projection.js +659 -0
- package/dist/app/collab-recovery.d.ts +25 -0
- package/dist/app/collab-recovery.js +429 -0
- package/dist/app/collab-resources.d.ts +16 -0
- package/dist/app/collab-resources.js +278 -0
- package/dist/app/collab-role-events.d.ts +22 -0
- package/dist/app/collab-role-events.js +329 -0
- package/dist/app/collab-role-orchestration.d.ts +53 -0
- package/dist/app/collab-role-orchestration.js +601 -0
- package/dist/app/collab-role-plan.d.ts +81 -0
- package/dist/app/collab-role-plan.js +504 -0
- package/dist/app/collab-role-repair.d.ts +109 -0
- package/dist/app/collab-role-repair.js +821 -0
- package/dist/app/collab-rollover.d.ts +263 -0
- package/dist/app/collab-rollover.js +1112 -0
- package/dist/app/collab-route.d.ts +64 -0
- package/dist/app/collab-route.js +240 -0
- package/dist/app/collab-session-transition.d.ts +221 -0
- package/dist/app/collab-session-transition.js +3334 -0
- package/dist/app/collab-state.d.ts +7 -0
- package/dist/app/collab-state.js +162 -0
- package/dist/app/collab-task-store.d.ts +22 -0
- package/dist/app/collab-task-store.js +204 -0
- package/dist/app/collab-work-execution.d.ts +127 -0
- package/dist/app/collab-work-execution.js +1027 -0
- package/dist/app/collab-work-plan.d.ts +87 -0
- package/dist/app/collab-work-plan.js +437 -0
- package/dist/app/collaboration.d.ts +8 -2
- package/dist/app/collaboration.js +101 -12
- package/dist/app/doctor.js +108 -5
- package/dist/app/host-mcp.js +34 -8
- package/dist/app/host-probe.js +4 -4
- package/dist/app/init.d.ts +58 -4
- package/dist/app/init.js +768 -155
- package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
- package/dist/app/managed-pi-bootstrap-admission.js +104 -0
- package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
- package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
- package/dist/app/managed-pi-delivery.d.ts +8 -0
- package/dist/app/managed-pi-delivery.js +110 -0
- package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
- package/dist/app/managed-pi-handoff-receiver.js +263 -0
- package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
- package/dist/app/managed-pi-handoff-reference.js +60 -0
- package/dist/app/managed-pi-herdr.d.ts +21 -0
- package/dist/app/managed-pi-herdr.js +185 -0
- package/dist/app/managed-pi-initial-admission.d.ts +28 -0
- package/dist/app/managed-pi-initial-admission.js +416 -0
- package/dist/app/managed-pi-initial-launch.d.ts +5 -0
- package/dist/app/managed-pi-initial-launch.js +61 -0
- package/dist/app/managed-pi-native-observation.d.ts +36 -0
- package/dist/app/managed-pi-native-observation.js +191 -0
- package/dist/app/managed-pi-readiness.d.ts +29 -0
- package/dist/app/managed-pi-readiness.js +288 -0
- package/dist/app/managed-pi-repair-stop.d.ts +21 -0
- package/dist/app/managed-pi-repair-stop.js +371 -0
- package/dist/app/managed-pi-resume-admission.d.ts +32 -0
- package/dist/app/managed-pi-resume-admission.js +618 -0
- package/dist/app/managed-pi-retirement.d.ts +28 -0
- package/dist/app/managed-pi-retirement.js +112 -0
- package/dist/app/managed-pi-sdk-control.d.ts +7 -0
- package/dist/app/managed-pi-sdk-control.js +169 -0
- package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
- package/dist/app/managed-pi-sdk-runtime.js +483 -0
- package/dist/app/managed-pi-source-admission.d.ts +14 -0
- package/dist/app/managed-pi-source-admission.js +323 -0
- package/dist/app/managed-pi-transition-host.d.ts +36 -0
- package/dist/app/managed-pi-transition-host.js +292 -0
- package/dist/app/managed-pi.d.ts +54 -0
- package/dist/app/managed-pi.js +248 -0
- package/dist/app/shell-readiness.d.ts +66 -0
- package/dist/app/shell-readiness.js +368 -0
- package/dist/app/task-pin.d.ts +2 -23
- package/dist/app/task-pin.js +2 -113
- package/dist/cli/commands/collaboration-binding.d.ts +13 -0
- package/dist/cli/commands/collaboration-binding.js +118 -0
- package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
- package/dist/cli/commands/collaboration-bootstrap.js +84 -0
- package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
- package/dist/cli/commands/collaboration-capabilities.js +127 -0
- package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
- package/dist/cli/commands/collaboration-handoff.js +102 -0
- package/dist/cli/commands/collaboration-message.d.ts +17 -0
- package/dist/cli/commands/collaboration-message.js +177 -0
- package/dist/cli/commands/collaboration-plan.d.ts +8 -0
- package/dist/cli/commands/collaboration-plan.js +108 -0
- package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
- package/dist/cli/commands/collaboration-preflight.js +167 -0
- package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
- package/dist/cli/commands/collaboration-recovery-run.js +224 -0
- package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
- package/dist/cli/commands/collaboration-recovery.js +104 -0
- package/dist/cli/commands/collaboration-resources.d.ts +11 -0
- package/dist/cli/commands/collaboration-resources.js +96 -0
- package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
- package/dist/cli/commands/collaboration-role-plan.js +69 -0
- package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
- package/dist/cli/commands/collaboration-role-run.js +128 -0
- package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
- package/dist/cli/commands/collaboration-rollover.js +103 -0
- package/dist/cli/commands/collaboration-state.d.ts +10 -0
- package/dist/cli/commands/collaboration-state.js +96 -0
- package/dist/cli/commands/collaboration-transition.d.ts +10 -0
- package/dist/cli/commands/collaboration-transition.js +75 -0
- package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
- package/dist/cli/commands/collaboration-work-run.js +178 -0
- package/dist/cli/commands/collaboration.d.ts +4 -0
- package/dist/cli/commands/collaboration.js +39 -4
- package/dist/cli/commands/continuity.js +2 -6
- package/dist/cli/commands/docs.d.ts +2 -0
- package/dist/cli/commands/docs.js +89 -15
- package/dist/cli/commands/host.d.ts +7 -1
- package/dist/cli/commands/host.js +122 -7
- package/dist/cli/commands/init.d.ts +20 -3
- package/dist/cli/commands/init.js +897 -232
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +11 -5
- package/dist/cli/commands/repair.d.ts +12 -0
- package/dist/cli/commands/repair.js +41 -0
- package/dist/cli/commands/runtime.d.ts +1 -1
- package/dist/cli/commands/runtime.js +139 -38
- package/dist/cli/commands/session.d.ts +7 -0
- package/dist/cli/commands/session.js +82 -0
- package/dist/cli/commands/task.js +19 -5
- package/dist/cli/run.js +866 -24
- package/dist/code/identity.d.ts +7 -0
- package/dist/code/identity.js +7 -3
- package/dist/code/registration.js +7 -0
- package/dist/code/service.d.ts +9 -0
- package/dist/code/service.js +16 -3
- package/dist/code/text.js +19 -7
- package/dist/collab/admission.d.ts +91 -66
- package/dist/collab/admission.js +21 -6
- package/dist/collab/canonical.d.ts +1 -1
- package/dist/collab/canonical.js +44 -3
- package/dist/collab/envelope.d.ts +2 -2
- package/dist/collab/envelope.js +12 -12
- package/dist/collab/first-work.d.ts +3 -0
- package/dist/collab/first-work.js +15 -0
- package/dist/collab/handoff.d.ts +16 -0
- package/dist/collab/handoff.js +39 -5
- package/dist/collab/index.d.ts +97 -1
- package/dist/collab/index.js +9 -1
- package/dist/collab/messages.js +126 -3
- package/dist/collab/persisted-messages.d.ts +123 -0
- package/dist/collab/persisted-messages.js +496 -0
- package/dist/collab/preflight.d.ts +1 -1
- package/dist/collab/resources.d.ts +13 -3
- package/dist/collab/resources.js +37 -6
- package/dist/collab/rollover.d.ts +311 -0
- package/dist/collab/rollover.js +1024 -0
- package/dist/collab/store.d.ts +1 -0
- package/dist/collab/store.js +1 -4
- package/dist/collab/types.d.ts +2 -354
- package/dist/collab/validation.d.ts +2 -10
- package/dist/collab/validation.js +2 -303
- package/dist/collab/work-plan.d.ts +105 -0
- package/dist/collab/work-plan.js +843 -0
- package/dist/continuity/context.d.ts +11 -2
- package/dist/continuity/context.js +100 -13
- package/dist/continuity/handoff-bundle.d.ts +296 -0
- package/dist/continuity/handoff-bundle.js +477 -0
- package/dist/continuity/index.d.ts +2 -0
- package/dist/continuity/index.js +1 -0
- package/dist/continuity/paths.js +10 -2
- package/dist/continuity/service.js +91 -15
- package/dist/continuity/task-pin.d.ts +24 -0
- package/dist/continuity/task-pin.js +590 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +6 -0
- package/dist/experimental/index.d.ts +13 -0
- package/dist/experimental/index.js +25 -0
- package/dist/experimental/pi.d.ts +4 -0
- package/dist/experimental/pi.js +12 -0
- package/dist/hosts/claude/native.js +1 -1
- package/dist/hosts/codex/native.js +1 -1
- package/dist/hosts/codex/protocol.js +1 -1
- package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
- package/dist/hosts/integration/bootstrap-config.js +254 -0
- package/dist/hosts/integration/bundle-export.d.ts +44 -0
- package/dist/hosts/integration/bundle-export.js +366 -3
- package/dist/hosts/integration/bundle.js +3 -0
- package/dist/hosts/integration/hook.js +36 -9
- package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
- package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
- package/dist/hosts/integration/pappra-hook.mjs +61 -24749
- package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
- package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
- package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
- package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
- package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
- package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
- package/dist/hosts/integration/pi-native-events.d.ts +112 -0
- package/dist/hosts/integration/pi-native-events.js +348 -0
- package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
- package/dist/hosts/integration/pi-rollover-extension.js +431 -0
- package/dist/hosts/integration/recovery-config.d.ts +48 -0
- package/dist/hosts/integration/recovery-config.js +117 -0
- package/dist/hosts/integration/service.d.ts +6 -1
- package/dist/hosts/integration/service.js +485 -35
- package/dist/hosts/integration/support.d.ts +7 -0
- package/dist/hosts/integration/support.js +9 -1
- package/dist/hosts/integration/templates.d.ts +3 -0
- package/dist/hosts/integration/templates.js +26 -3
- package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
- package/dist/hosts/pi/managed-host-identity.js +40 -0
- package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
- package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
- package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
- package/dist/hosts/pi/managed-sdk-session.js +1639 -0
- package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
- package/dist/hosts/pi/managed-session-extension.js +84 -0
- package/dist/hosts/pi/managed-session.d.ts +177 -0
- package/dist/hosts/pi/managed-session.js +1557 -0
- package/dist/hosts/pi/protocol.js +1 -1
- package/dist/hosts/pi/query-result.js +1 -1
- package/dist/hosts/pi/resume-capability.d.ts +41 -0
- package/dist/hosts/pi/resume-capability.js +33 -0
- package/dist/hosts/pi/rpc.d.ts +182 -134
- package/dist/hosts/pi/rpc.js +230 -37
- package/dist/hosts/pilot.d.ts +97 -74
- package/dist/hosts/pilot.js +5 -1
- package/dist/hosts/version.d.ts +3 -0
- package/dist/hosts/version.js +18 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/bounded-file.d.ts +19 -0
- package/dist/infra/bounded-file.js +66 -0
- package/dist/infra/fs.js +10 -2
- package/dist/infra/git.d.ts +29 -0
- package/dist/infra/git.js +45 -9
- package/dist/infra/json-property.d.ts +2 -0
- package/dist/infra/json-property.js +69 -0
- package/dist/infra/json.d.ts +4 -0
- package/dist/infra/json.js +24 -0
- package/dist/infra/owned-process-group.js +1 -1
- package/dist/infra/private-lock.d.ts +16 -0
- package/dist/infra/private-lock.js +148 -0
- package/dist/infra/private-state.d.ts +7 -0
- package/dist/infra/private-state.js +146 -0
- package/dist/infra/process-identity.d.ts +12 -0
- package/dist/infra/process-identity.js +142 -0
- package/dist/infra/validation.d.ts +5 -0
- package/dist/infra/validation.js +15 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +6 -0
- package/dist/install/apply.d.ts +9 -1
- package/dist/install/apply.js +176 -12
- package/dist/install/dependencies.d.ts +38 -0
- package/dist/install/dependencies.js +282 -0
- package/dist/install/ownership.js +30 -1
- package/dist/install/references.d.ts +31 -0
- package/dist/install/references.js +208 -0
- package/dist/install/repair.d.ts +37 -0
- package/dist/install/repair.js +798 -0
- package/dist/install/reset-local.d.ts +16 -0
- package/dist/install/reset-local.js +304 -0
- package/dist/install/rollback.d.ts +3 -0
- package/dist/install/rollback.js +9 -3
- package/dist/install/runtime.d.ts +10 -6
- package/dist/install/runtime.js +299 -59
- package/dist/install/source-fingerprint.d.ts +2 -0
- package/dist/install/source-fingerprint.js +103 -0
- package/dist/knowledge/catalog.d.ts +1 -0
- package/dist/knowledge/catalog.js +143 -10
- package/dist/knowledge/journal.js +17 -7
- package/dist/knowledge/paths.js +2 -1
- package/dist/knowledge/refs.js +4 -0
- package/dist/knowledge/register-batch.d.ts +4 -0
- package/dist/knowledge/register-batch.js +225 -0
- package/dist/knowledge/relocate.d.ts +5 -0
- package/dist/knowledge/relocate.js +92 -0
- package/dist/knowledge/scan.js +103 -47
- package/dist/knowledge/service.d.ts +3 -0
- package/dist/knowledge/service.js +33 -1
- package/dist/knowledge/write.d.ts +4 -1
- package/dist/knowledge/write.js +1 -1
- package/dist/mcp/server.d.ts +4 -2
- package/dist/mcp/server.js +87 -136
- package/dist/schemas/collaboration-state-validation.d.ts +6 -0
- package/dist/schemas/collaboration-state-validation.js +342 -0
- package/dist/schemas/collaboration-state.d.ts +436 -0
- package/dist/schemas/collaboration-state.js +1 -0
- package/dist/schemas/collaboration.d.ts +335 -0
- package/dist/schemas/collaboration.js +272 -0
- package/dist/schemas/continuity.d.ts +8 -0
- package/dist/schemas/continuity.js +13 -3
- package/dist/schemas/doctor.d.ts +1 -0
- package/dist/schemas/doctor.js +1 -0
- package/dist/schemas/document.d.ts +61 -0
- package/dist/schemas/document.js +9 -0
- package/dist/schemas/errors.d.ts +1 -0
- package/dist/schemas/errors.js +1 -0
- package/dist/schemas/host.d.ts +24 -0
- package/dist/schemas/host.js +19 -0
- package/dist/schemas/install.d.ts +8 -2
- package/dist/schemas/install.js +11 -1
- package/dist/schemas/managed-pi-execution.d.ts +16 -0
- package/dist/schemas/managed-pi-execution.js +2 -0
- package/dist/schemas/outcome.d.ts +9 -5
- package/dist/workspace/config-file.js +1 -3
- package/dist/workspace/documents.js +12 -2
- package/dist/workspace/repositories.d.ts +7 -0
- package/dist/workspace/repositories.js +14 -5
- package/package.json +35 -35
- package/skills/pappra/SKILL.md +28 -7
- package/skills/pappra/references/collaboration.md +633 -0
- package/skills/pappra/references/environment.md +360 -0
- package/skills/pappra/references/hosts.md +20 -0
- package/skills/pappra/references/operations.md +36 -5
- package/dist/app/collab-from.d.ts +0 -35
- package/dist/app/collab-from.js +0 -84
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import { object as isRecord } from '../infra/validation.js';
|
|
2
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { aggregateRolePlan, expandRolePlan, } from './collab-role-plan.js';
|
|
6
|
+
import { inspectWorkPlan, } from './collab-work-plan.js';
|
|
7
|
+
import { runWorkExecution, } from './collab-work-execution.js';
|
|
8
|
+
import { validateWorkPlan } from '../collab/work-plan.js';
|
|
9
|
+
import { readBoundedFile } from './bounded-file.js';
|
|
10
|
+
import { recoverRoleRepairPlan, repairRejectedRoleNode, } from './collab-role-repair.js';
|
|
11
|
+
export const COLLAB_ROLE_ORCHESTRATION_SCHEMA = 'pappra/collab-role-orchestration/1';
|
|
12
|
+
const JOURNAL_SCHEMA = 'pappra/collab-role-orchestration-journal/1';
|
|
13
|
+
const MAX_JOURNAL_BYTES = 1024 * 1024;
|
|
14
|
+
const MAX_EVENTS_PER_PLAN = 256;
|
|
15
|
+
const MAX_ACTION_LIMIT = 64;
|
|
16
|
+
const MAX_ROUNDS_LIMIT = 64;
|
|
17
|
+
const MAX_EVENT_ID = 256;
|
|
18
|
+
function digest(value) {
|
|
19
|
+
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
20
|
+
}
|
|
21
|
+
function failure(request, status, diagnostic, round = null) {
|
|
22
|
+
return {
|
|
23
|
+
schema: COLLAB_ROLE_ORCHESTRATION_SCHEMA,
|
|
24
|
+
status,
|
|
25
|
+
event_id: request?.event_id ?? '',
|
|
26
|
+
plan_id: request?.role_plan?.plan_id ?? '',
|
|
27
|
+
round,
|
|
28
|
+
reserved_actions: 0,
|
|
29
|
+
prior_outcome: null,
|
|
30
|
+
aggregate: null,
|
|
31
|
+
execution: null,
|
|
32
|
+
updatedRequest: null,
|
|
33
|
+
diagnostics: [diagnostic],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function validateJournal(value) {
|
|
37
|
+
if (!isRecord(value) ||
|
|
38
|
+
value['schema'] !== JOURNAL_SCHEMA ||
|
|
39
|
+
!Array.isArray(value['plans']) ||
|
|
40
|
+
!Array.isArray(value['events']) ||
|
|
41
|
+
value['plans'].length > MAX_EVENTS_PER_PLAN ||
|
|
42
|
+
value['events'].length > MAX_EVENTS_PER_PLAN * MAX_ROUNDS_LIMIT)
|
|
43
|
+
return null;
|
|
44
|
+
for (const plan of value['plans']) {
|
|
45
|
+
if (!isRecord(plan) ||
|
|
46
|
+
typeof plan['plan_id'] !== 'string' ||
|
|
47
|
+
!Number.isSafeInteger(plan['max_rounds']) ||
|
|
48
|
+
!Number.isSafeInteger(plan['max_actions']) ||
|
|
49
|
+
!Number.isSafeInteger(plan['rounds']) ||
|
|
50
|
+
!Number.isSafeInteger(plan['reserved_actions']))
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
for (const event of value['events']) {
|
|
54
|
+
if (!isRecord(event) ||
|
|
55
|
+
typeof event['plan_id'] !== 'string' ||
|
|
56
|
+
typeof event['event_id'] !== 'string' ||
|
|
57
|
+
typeof event['request_digest'] !== 'string' ||
|
|
58
|
+
!Number.isSafeInteger(event['reserved_actions']) ||
|
|
59
|
+
(event['state'] !== 'reserved' && event['state'] !== 'complete') ||
|
|
60
|
+
!(event['outcome'] === null || typeof event['outcome'] === 'string'))
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function readJournal(file) {
|
|
66
|
+
if (!fs.existsSync(file))
|
|
67
|
+
return null;
|
|
68
|
+
const read = readBoundedFile(file, MAX_JOURNAL_BYTES);
|
|
69
|
+
if (!read.ok)
|
|
70
|
+
return undefined;
|
|
71
|
+
try {
|
|
72
|
+
const parsed = validateJournal(JSON.parse(read.text));
|
|
73
|
+
return parsed ?? undefined;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function withJournalLock(file, action) {
|
|
80
|
+
const lockFile = `${file}.lock`;
|
|
81
|
+
let lock;
|
|
82
|
+
try {
|
|
83
|
+
lock = fs.openSync(lockFile, 'wx', 0o600);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
fs.writeFileSync(lock, `${process.pid}\n`, 'utf8');
|
|
90
|
+
fs.fsyncSync(lock);
|
|
91
|
+
return action();
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
fs.closeSync(lock);
|
|
95
|
+
try {
|
|
96
|
+
fs.unlinkSync(lockFile);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// A failed lock cleanup fails closed on the next tick.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function writeJournal(file, journal) {
|
|
104
|
+
const temporary = `${file}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
105
|
+
let descriptor = null;
|
|
106
|
+
try {
|
|
107
|
+
descriptor = fs.openSync(temporary, 'wx', 0o600);
|
|
108
|
+
fs.writeFileSync(descriptor, `${JSON.stringify(journal)}\n`, 'utf8');
|
|
109
|
+
fs.fsyncSync(descriptor);
|
|
110
|
+
fs.closeSync(descriptor);
|
|
111
|
+
descriptor = null;
|
|
112
|
+
fs.renameSync(temporary, file);
|
|
113
|
+
const parent = fs.openSync(path.dirname(file), 'r');
|
|
114
|
+
try {
|
|
115
|
+
fs.fsyncSync(parent);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
fs.closeSync(parent);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
if (descriptor !== null)
|
|
123
|
+
fs.closeSync(descriptor);
|
|
124
|
+
try {
|
|
125
|
+
fs.unlinkSync(temporary);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// The rename already consumed it or cleanup is best effort.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function reserveTick(request, requestDigest, actions) {
|
|
133
|
+
const file = request.journal_file;
|
|
134
|
+
const taskRoot = path.resolve(request.execution.task_root);
|
|
135
|
+
const absolute = path.resolve(file);
|
|
136
|
+
const relative = path.relative(taskRoot, absolute);
|
|
137
|
+
if (!path.isAbsolute(file) ||
|
|
138
|
+
relative === '' ||
|
|
139
|
+
relative.startsWith('..') ||
|
|
140
|
+
path.isAbsolute(relative))
|
|
141
|
+
return {
|
|
142
|
+
kind: 'invalid',
|
|
143
|
+
reason: 'journal_file_must_be_a_task_local_absolute_path',
|
|
144
|
+
};
|
|
145
|
+
try {
|
|
146
|
+
const parent = fs.lstatSync(path.dirname(absolute));
|
|
147
|
+
if (!parent.isDirectory() || parent.isSymbolicLink())
|
|
148
|
+
return {
|
|
149
|
+
kind: 'invalid',
|
|
150
|
+
reason: 'journal_parent_must_be_a_real_directory',
|
|
151
|
+
};
|
|
152
|
+
const existing = fs.existsSync(absolute) ? fs.lstatSync(absolute) : null;
|
|
153
|
+
if (existing && (!existing.isFile() || existing.isSymbolicLink()))
|
|
154
|
+
return { kind: 'invalid', reason: 'journal_must_be_a_regular_file' };
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return { kind: 'invalid', reason: 'journal_parent_unavailable' };
|
|
158
|
+
}
|
|
159
|
+
let result;
|
|
160
|
+
try {
|
|
161
|
+
result = withJournalLock(absolute, () => {
|
|
162
|
+
const loaded = readJournal(absolute);
|
|
163
|
+
if (loaded === undefined)
|
|
164
|
+
return {
|
|
165
|
+
kind: 'invalid',
|
|
166
|
+
reason: 'journal_unreadable_or_invalid',
|
|
167
|
+
};
|
|
168
|
+
const journal = loaded ?? {
|
|
169
|
+
schema: JOURNAL_SCHEMA,
|
|
170
|
+
plans: [],
|
|
171
|
+
events: [],
|
|
172
|
+
};
|
|
173
|
+
const matching = journal.events.find((event) => event.plan_id === request.role_plan.plan_id &&
|
|
174
|
+
event.event_id === request.event_id);
|
|
175
|
+
if (matching) {
|
|
176
|
+
if (matching.request_digest !== requestDigest)
|
|
177
|
+
return {
|
|
178
|
+
kind: 'invalid',
|
|
179
|
+
reason: 'event_id_reused_for_different_request',
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
kind: 'duplicate',
|
|
183
|
+
state: matching.state,
|
|
184
|
+
outcome: matching.outcome,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (journal.events.length >= MAX_EVENTS_PER_PLAN * MAX_ROUNDS_LIMIT)
|
|
188
|
+
return { kind: 'budget', round: null };
|
|
189
|
+
const existingPlan = journal.plans.find((plan) => plan.plan_id === request.role_plan.plan_id);
|
|
190
|
+
const plan = existingPlan ?? {
|
|
191
|
+
plan_id: request.role_plan.plan_id,
|
|
192
|
+
max_rounds: request.limits.max_rounds,
|
|
193
|
+
max_actions: request.limits.max_actions,
|
|
194
|
+
rounds: 0,
|
|
195
|
+
reserved_actions: 0,
|
|
196
|
+
};
|
|
197
|
+
if (plan.max_rounds !== request.limits.max_rounds ||
|
|
198
|
+
plan.max_actions !== request.limits.max_actions)
|
|
199
|
+
return {
|
|
200
|
+
kind: 'invalid',
|
|
201
|
+
reason: 'plan_limits_cannot_change_after_first_tick',
|
|
202
|
+
};
|
|
203
|
+
if (plan.rounds >= plan.max_rounds)
|
|
204
|
+
return { kind: 'budget', round: plan.rounds };
|
|
205
|
+
const reservedActions = Math.min(actions, plan.max_actions - plan.reserved_actions);
|
|
206
|
+
const nextPlan = {
|
|
207
|
+
...plan,
|
|
208
|
+
rounds: plan.rounds + 1,
|
|
209
|
+
reserved_actions: plan.reserved_actions + reservedActions,
|
|
210
|
+
};
|
|
211
|
+
const event = {
|
|
212
|
+
plan_id: request.role_plan.plan_id,
|
|
213
|
+
event_id: request.event_id,
|
|
214
|
+
request_digest: requestDigest,
|
|
215
|
+
reserved_actions: reservedActions,
|
|
216
|
+
state: 'reserved',
|
|
217
|
+
outcome: null,
|
|
218
|
+
};
|
|
219
|
+
const next = {
|
|
220
|
+
schema: JOURNAL_SCHEMA,
|
|
221
|
+
plans: existingPlan
|
|
222
|
+
? journal.plans.map((entry) => entry.plan_id === nextPlan.plan_id ? nextPlan : entry)
|
|
223
|
+
: [...journal.plans, nextPlan],
|
|
224
|
+
events: [...journal.events, event],
|
|
225
|
+
};
|
|
226
|
+
writeJournal(absolute, next);
|
|
227
|
+
return {
|
|
228
|
+
kind: 'reserved',
|
|
229
|
+
round: nextPlan.rounds,
|
|
230
|
+
actions: reservedActions,
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return { kind: 'invalid', reason: 'journal_reservation_write_failed' };
|
|
236
|
+
}
|
|
237
|
+
return result ?? { kind: 'locked' };
|
|
238
|
+
}
|
|
239
|
+
function completeTick(request, requestDigest, outcome) {
|
|
240
|
+
const file = path.resolve(request.journal_file);
|
|
241
|
+
try {
|
|
242
|
+
const updated = withJournalLock(file, () => {
|
|
243
|
+
const journal = readJournal(file);
|
|
244
|
+
if (!journal)
|
|
245
|
+
return false;
|
|
246
|
+
const event = journal.events.find((entry) => entry.plan_id === request.role_plan.plan_id &&
|
|
247
|
+
entry.event_id === request.event_id);
|
|
248
|
+
if (!event || event.request_digest !== requestDigest)
|
|
249
|
+
return false;
|
|
250
|
+
event.state = 'complete';
|
|
251
|
+
event.outcome = outcome;
|
|
252
|
+
writeJournal(file, journal);
|
|
253
|
+
return true;
|
|
254
|
+
});
|
|
255
|
+
return updated === true;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function samePlan(left, right) {
|
|
262
|
+
return (left.schema === right.schema &&
|
|
263
|
+
left.plan_id === right.plan_id &&
|
|
264
|
+
left.task_id === right.task_id &&
|
|
265
|
+
left.owner_epoch === right.owner_epoch &&
|
|
266
|
+
left.nodes.length === right.nodes.length &&
|
|
267
|
+
left.nodes.every((node, index) => {
|
|
268
|
+
const other = right.nodes[index];
|
|
269
|
+
return (other !== undefined &&
|
|
270
|
+
node.node_id === other.node_id &&
|
|
271
|
+
node.role === other.role &&
|
|
272
|
+
node.assignment_id === other.assignment_id &&
|
|
273
|
+
node.attempt === other.attempt &&
|
|
274
|
+
node.input_revision === other.input_revision &&
|
|
275
|
+
node.assignee.agent_id === other.assignee.agent_id &&
|
|
276
|
+
node.assignee.session_id === other.assignee.session_id &&
|
|
277
|
+
JSON.stringify(node.depends_on) === JSON.stringify(other.depends_on) &&
|
|
278
|
+
JSON.stringify(node.resources) === JSON.stringify(other.resources));
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
function matchingExpansion(report, expansion, taskRoot, planFile) {
|
|
282
|
+
if (!report.ok ||
|
|
283
|
+
report.status !== 'evaluated' ||
|
|
284
|
+
report.stale ||
|
|
285
|
+
path.resolve(report.task_root ?? '') !== path.resolve(taskRoot) ||
|
|
286
|
+
report.plan.ref !== planFile ||
|
|
287
|
+
report.plan.plan_id !== expansion.plan.plan_id ||
|
|
288
|
+
report.plan.owner_epoch !== expansion.plan.owner_epoch ||
|
|
289
|
+
report.task_id !== expansion.plan.task_id ||
|
|
290
|
+
report.core === null ||
|
|
291
|
+
report.core.ok !== true ||
|
|
292
|
+
report.core.plan_id !== expansion.plan.plan_id ||
|
|
293
|
+
report.core.task_id !== expansion.plan.task_id ||
|
|
294
|
+
report.core.owner_epoch !== expansion.plan.owner_epoch)
|
|
295
|
+
return false;
|
|
296
|
+
const read = readBoundedFile(planFile, 512 * 1024);
|
|
297
|
+
if (!read.ok)
|
|
298
|
+
return false;
|
|
299
|
+
try {
|
|
300
|
+
const parsed = validateWorkPlan(JSON.parse(read.text));
|
|
301
|
+
return parsed.ok && samePlan(parsed.plan, expansion.plan);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function result(request, status, round, reservedActions, values = {}) {
|
|
308
|
+
return {
|
|
309
|
+
schema: COLLAB_ROLE_ORCHESTRATION_SCHEMA,
|
|
310
|
+
status,
|
|
311
|
+
event_id: request.event_id,
|
|
312
|
+
plan_id: request.role_plan.plan_id,
|
|
313
|
+
round,
|
|
314
|
+
reserved_actions: reservedActions,
|
|
315
|
+
prior_outcome: values.prior_outcome ?? null,
|
|
316
|
+
aggregate: values.aggregate ?? null,
|
|
317
|
+
execution: values.execution ?? null,
|
|
318
|
+
updatedRequest: values.updatedRequest ?? null,
|
|
319
|
+
diagnostics: values.diagnostics ?? [],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Advances one explicit session-event tick over the existing role-plan and WorkPlan state.
|
|
324
|
+
* The durable journal reserves the event and conservative action budget before execution;
|
|
325
|
+
* the task record, registered envelopes, and WorkPlan evaluator remain the only authority.
|
|
326
|
+
*/
|
|
327
|
+
export async function tickRoleOrchestration(request, deps = {}) {
|
|
328
|
+
if (!request ||
|
|
329
|
+
!request.role_plan ||
|
|
330
|
+
!request.execution ||
|
|
331
|
+
typeof request.journal_file !== 'string' ||
|
|
332
|
+
!Array.isArray(request.execution.capability_cards) ||
|
|
333
|
+
typeof request.event_id !== 'string' ||
|
|
334
|
+
request.event_id.length === 0 ||
|
|
335
|
+
request.event_id.length > MAX_EVENT_ID ||
|
|
336
|
+
!request.limits ||
|
|
337
|
+
!Number.isSafeInteger(request.limits.max_rounds) ||
|
|
338
|
+
request.limits.max_rounds < 1 ||
|
|
339
|
+
request.limits.max_rounds > MAX_ROUNDS_LIMIT ||
|
|
340
|
+
!Number.isSafeInteger(request.limits.max_actions) ||
|
|
341
|
+
request.limits.max_actions < 0 ||
|
|
342
|
+
request.limits.max_actions > MAX_ACTION_LIMIT ||
|
|
343
|
+
!Number.isSafeInteger(request.limits.max_attempts) ||
|
|
344
|
+
request.limits.max_attempts < 1 ||
|
|
345
|
+
request.limits.max_attempts > 1000)
|
|
346
|
+
return failure(request, 'blocked', 'request_or_limits_invalid');
|
|
347
|
+
const immutableRequest = request;
|
|
348
|
+
if (request.repair_policy &&
|
|
349
|
+
Array.isArray(request.repair_policy.authorizations)) {
|
|
350
|
+
const recovered = await recoverRoleRepairPlan(immutableRequest.role_plan, immutableRequest.execution, immutableRequest.repair_policy, immutableRequest.journal_file, immutableRequest.execution.task_root, immutableRequest.execution.plan_file, immutableRequest.limits.max_attempts);
|
|
351
|
+
request = {
|
|
352
|
+
...request,
|
|
353
|
+
role_plan: recovered.role_plan,
|
|
354
|
+
execution: recovered.execution,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const expansion = expandRolePlan(request.role_plan);
|
|
358
|
+
if (!expansion.ok)
|
|
359
|
+
return failure(request, 'blocked', 'role_plan_expansion_refused');
|
|
360
|
+
if (expansion.plan.task_id === '' ||
|
|
361
|
+
request.execution.plan_file === '' ||
|
|
362
|
+
request.execution.task_root === '' ||
|
|
363
|
+
!path.isAbsolute(request.execution.task_root) ||
|
|
364
|
+
!path.isAbsolute(request.execution.plan_file))
|
|
365
|
+
return failure(request, 'blocked', 'execution_paths_invalid');
|
|
366
|
+
const requestDigest = digest({
|
|
367
|
+
role_plan: immutableRequest.role_plan,
|
|
368
|
+
execution: {
|
|
369
|
+
task_root: immutableRequest.execution.task_root,
|
|
370
|
+
plan_file: immutableRequest.execution.plan_file,
|
|
371
|
+
binding_file: immutableRequest.execution.binding_file,
|
|
372
|
+
caller: immutableRequest.execution.caller,
|
|
373
|
+
caller_pane: immutableRequest.execution.caller_pane,
|
|
374
|
+
capability_cards: immutableRequest.execution.capability_cards,
|
|
375
|
+
max_concurrency: immutableRequest.execution.max_concurrency ?? 2,
|
|
376
|
+
},
|
|
377
|
+
limits: immutableRequest.limits,
|
|
378
|
+
repair_policy: immutableRequest.repair_policy ?? null,
|
|
379
|
+
});
|
|
380
|
+
const inspect = deps.inspect ?? inspectWorkPlan;
|
|
381
|
+
const execute = deps.execute ?? runWorkExecution;
|
|
382
|
+
// Determine the maximum possible ready work from the explicit plan before reserving.
|
|
383
|
+
// The authoritative inspection is repeated after reservation, immediately before effects.
|
|
384
|
+
let initial;
|
|
385
|
+
try {
|
|
386
|
+
initial = await inspect({
|
|
387
|
+
taskRoot: request.execution.task_root,
|
|
388
|
+
planFile: request.execution.plan_file,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
return failure(request, 'blocked', 'authoritative_plan_inspection_failed');
|
|
393
|
+
}
|
|
394
|
+
if (!matchingExpansion(initial, expansion, request.execution.task_root, request.execution.plan_file))
|
|
395
|
+
return failure(request, initial.status === 'incomplete' ? 'reconciliation-required' : 'blocked', 'authoritative_plan_inspection_unavailable_or_mismatched');
|
|
396
|
+
const initialReady = [
|
|
397
|
+
...(initial.core?.ready_nodes ?? []),
|
|
398
|
+
...initial.nodes
|
|
399
|
+
.filter((node) => node.dispatch_pending === true)
|
|
400
|
+
.map((node) => node.node_id),
|
|
401
|
+
];
|
|
402
|
+
const providedAssignments = new Set(request.execution.capability_cards.map((card) => card.assignment_id));
|
|
403
|
+
const candidateAssignments = new Set(expansion.plan.nodes
|
|
404
|
+
.filter((node) => initialReady.includes(node.node_id) &&
|
|
405
|
+
providedAssignments.has(node.assignment_id))
|
|
406
|
+
.map((node) => node.assignment_id));
|
|
407
|
+
const candidateCards = request.execution.capability_cards.filter((card) => candidateAssignments.has(card.assignment_id));
|
|
408
|
+
const initialAggregate = initial.core === null ? null : aggregateRolePlan(expansion, initial.core);
|
|
409
|
+
const actionsToReserve = Math.min(initialAggregate?.rejected_nodes.length && request.repair_policy
|
|
410
|
+
? 1
|
|
411
|
+
: candidateCards.length, request.limits.max_actions);
|
|
412
|
+
const reservation = reserveTick(request, requestDigest, actionsToReserve);
|
|
413
|
+
if (reservation.kind === 'invalid')
|
|
414
|
+
return failure(request, 'blocked', reservation.reason);
|
|
415
|
+
if (reservation.kind === 'locked')
|
|
416
|
+
return failure(request, 'reconciliation-required', 'journal_lock_present_or_busy');
|
|
417
|
+
if (reservation.kind === 'budget')
|
|
418
|
+
return failure(request, 'budget-exhausted', 'round_or_action_budget_exhausted', reservation.round);
|
|
419
|
+
if (reservation.kind === 'duplicate')
|
|
420
|
+
return result(request, 'duplicate', null, 0, {
|
|
421
|
+
prior_outcome: reservation.state === 'reserved'
|
|
422
|
+
? 'reserved_unknown_reconcile_before_new_event'
|
|
423
|
+
: (reservation.outcome ?? 'completed'),
|
|
424
|
+
});
|
|
425
|
+
const round = reservation.round;
|
|
426
|
+
const finish = (status, values = {}) => {
|
|
427
|
+
const saved = completeTick(request, requestDigest, status);
|
|
428
|
+
return result(request, saved ? status : 'reconciliation-required', round, reservation.actions, {
|
|
429
|
+
...values,
|
|
430
|
+
diagnostics: saved
|
|
431
|
+
? (values.diagnostics ?? [])
|
|
432
|
+
: [...(values.diagnostics ?? []), 'journal_completion_unavailable'],
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
const repairRejected = async (aggregate, execution = null) => {
|
|
436
|
+
if (!request.repair_policy) {
|
|
437
|
+
const attempts = expansion.plan.nodes
|
|
438
|
+
.filter((node) => aggregate.rejected_nodes.includes(node.node_id))
|
|
439
|
+
.map((node) => node.attempt);
|
|
440
|
+
const exhausted = attempts.some((attempt) => attempt >= request.limits.max_attempts);
|
|
441
|
+
return finish(exhausted ? 'repair-exhausted' : 'repair-required', {
|
|
442
|
+
aggregate,
|
|
443
|
+
execution,
|
|
444
|
+
diagnostics: [
|
|
445
|
+
exhausted
|
|
446
|
+
? 'rejected_attempt_limit_reached'
|
|
447
|
+
: 'rejected_work_requires_a_new_authoritatively_assigned_attempt_and_plan',
|
|
448
|
+
],
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (reservation.actions < 1)
|
|
452
|
+
return finish('budget-exhausted', {
|
|
453
|
+
aggregate,
|
|
454
|
+
...(execution ? { execution } : {}),
|
|
455
|
+
diagnostics: ['repair_action_budget_exhausted'],
|
|
456
|
+
});
|
|
457
|
+
const repair = await repairRejectedRoleNode({
|
|
458
|
+
task_root: request.execution.task_root,
|
|
459
|
+
plan_file: request.execution.plan_file,
|
|
460
|
+
journal_file: request.journal_file,
|
|
461
|
+
event_id: request.event_id,
|
|
462
|
+
role_plan: request.role_plan,
|
|
463
|
+
execution: request.execution,
|
|
464
|
+
policy: request.repair_policy,
|
|
465
|
+
rejected_node_ids: aggregate.rejected_nodes,
|
|
466
|
+
max_attempts: request.limits.max_attempts,
|
|
467
|
+
max_actions: request.limits.max_actions,
|
|
468
|
+
reserved_actions: reservation.actions,
|
|
469
|
+
}, deps.repair);
|
|
470
|
+
if (repair.status === 'repaired' && repair.role_plan) {
|
|
471
|
+
return finish('repaired', {
|
|
472
|
+
aggregate,
|
|
473
|
+
execution,
|
|
474
|
+
updatedRequest: {
|
|
475
|
+
...request,
|
|
476
|
+
role_plan: repair.role_plan,
|
|
477
|
+
execution: repair.execution ?? request.execution,
|
|
478
|
+
},
|
|
479
|
+
diagnostics: [repair.diagnostic],
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return finish(repair.status, {
|
|
483
|
+
aggregate,
|
|
484
|
+
execution,
|
|
485
|
+
diagnostics: [repair.diagnostic],
|
|
486
|
+
});
|
|
487
|
+
};
|
|
488
|
+
let current;
|
|
489
|
+
try {
|
|
490
|
+
current = await inspect({
|
|
491
|
+
taskRoot: request.execution.task_root,
|
|
492
|
+
planFile: request.execution.plan_file,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return finish('reconciliation-required', {
|
|
497
|
+
diagnostics: ['authoritative_inspection_failed_after_tick_reservation'],
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (!matchingExpansion(current, expansion, request.execution.task_root, request.execution.plan_file))
|
|
501
|
+
return finish(current.status === 'incomplete' ? 'reconciliation-required' : 'blocked', {
|
|
502
|
+
diagnostics: ['plan_or_authoritative_evidence_changed'],
|
|
503
|
+
});
|
|
504
|
+
const aggregate = aggregateRolePlan(expansion, current.core);
|
|
505
|
+
if (aggregate.outcome === 'accepted')
|
|
506
|
+
return finish('accepted', { aggregate });
|
|
507
|
+
if (aggregate.rejected_nodes.length > 0)
|
|
508
|
+
return repairRejected(aggregate);
|
|
509
|
+
if (aggregate.outcome === 'unknown' || aggregate.outcome === 'missing')
|
|
510
|
+
return finish('reconciliation-required', {
|
|
511
|
+
aggregate,
|
|
512
|
+
diagnostics: ['authoritative_evidence_incomplete_or_uncertain'],
|
|
513
|
+
});
|
|
514
|
+
const ready = [
|
|
515
|
+
...(current.core?.ready_nodes ?? []),
|
|
516
|
+
...current.nodes
|
|
517
|
+
.filter((node) => node.dispatch_pending === true)
|
|
518
|
+
.map((node) => node.node_id),
|
|
519
|
+
];
|
|
520
|
+
const selectedNodes = expansion.plan.nodes
|
|
521
|
+
.filter((node) => ready.includes(node.node_id) &&
|
|
522
|
+
candidateAssignments.has(node.assignment_id))
|
|
523
|
+
.slice(0, reservation.actions);
|
|
524
|
+
if (selectedNodes.length === 0)
|
|
525
|
+
return finish(reservation.actions === 0
|
|
526
|
+
? 'waiting'
|
|
527
|
+
: aggregate.outcome === 'blocked'
|
|
528
|
+
? 'blocked'
|
|
529
|
+
: 'waiting', {
|
|
530
|
+
aggregate,
|
|
531
|
+
diagnostics: [
|
|
532
|
+
reservation.actions === 0
|
|
533
|
+
? candidateCards.length === 0
|
|
534
|
+
? 'no_pending_role_dispatch'
|
|
535
|
+
: 'action_budget_exhausted_for_this_plan'
|
|
536
|
+
: 'no_authoritatively_ready_assigned_role',
|
|
537
|
+
],
|
|
538
|
+
});
|
|
539
|
+
const selectedAssignments = new Set(selectedNodes.map((node) => node.assignment_id));
|
|
540
|
+
const selectedCards = request.execution.capability_cards.filter((card) => selectedAssignments.has(card.assignment_id));
|
|
541
|
+
if (selectedCards.length !== selectedNodes.length)
|
|
542
|
+
return finish('blocked', {
|
|
543
|
+
aggregate,
|
|
544
|
+
diagnostics: ['ready_assignment_missing_explicit_capability_card'],
|
|
545
|
+
});
|
|
546
|
+
let execution;
|
|
547
|
+
try {
|
|
548
|
+
execution = await execute({
|
|
549
|
+
request: {
|
|
550
|
+
...request.execution,
|
|
551
|
+
capability_cards: selectedCards,
|
|
552
|
+
max_concurrency: Math.min(request.execution.max_concurrency ?? 2, selectedNodes.length),
|
|
553
|
+
},
|
|
554
|
+
apply: true,
|
|
555
|
+
}, deps.execution_adapter ? { adapter: deps.execution_adapter } : undefined);
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
// The tick reservation deliberately stays unresolved. A fresh event must inspect evidence;
|
|
559
|
+
// this event is never replayed because a native outcome may be unknown.
|
|
560
|
+
return result(request, 'reconciliation-required', round, reservation.actions, {
|
|
561
|
+
aggregate,
|
|
562
|
+
diagnostics: ['execution_threw_after_durable_tick_reservation'],
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
let after;
|
|
566
|
+
try {
|
|
567
|
+
after = await inspect({
|
|
568
|
+
taskRoot: request.execution.task_root,
|
|
569
|
+
planFile: request.execution.plan_file,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return finish('reconciliation-required', {
|
|
574
|
+
aggregate,
|
|
575
|
+
execution,
|
|
576
|
+
diagnostics: ['post_execution_inspection_failed'],
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (!matchingExpansion(after, expansion, request.execution.task_root, request.execution.plan_file))
|
|
580
|
+
return finish('reconciliation-required', {
|
|
581
|
+
aggregate,
|
|
582
|
+
execution,
|
|
583
|
+
diagnostics: ['post_execution_evidence_unavailable_or_mismatched'],
|
|
584
|
+
});
|
|
585
|
+
const finalAggregate = aggregateRolePlan(expansion, after.core);
|
|
586
|
+
if (finalAggregate.outcome === 'accepted')
|
|
587
|
+
return finish('accepted', { aggregate: finalAggregate, execution });
|
|
588
|
+
if (finalAggregate.rejected_nodes.length > 0)
|
|
589
|
+
return repairRejected(finalAggregate, execution);
|
|
590
|
+
if (execution.status === 'needs-reconciliation')
|
|
591
|
+
return finish('reconciliation-required', {
|
|
592
|
+
aggregate: finalAggregate,
|
|
593
|
+
execution,
|
|
594
|
+
diagnostics: ['work_execution_requires_reconciliation'],
|
|
595
|
+
});
|
|
596
|
+
if (execution.status === 'blocked')
|
|
597
|
+
return finish('blocked', { aggregate: finalAggregate, execution });
|
|
598
|
+
if (execution.nodes.some((node) => node.execution_status === 'dispatched'))
|
|
599
|
+
return finish('dispatched', { aggregate: finalAggregate, execution });
|
|
600
|
+
return finish('waiting', { aggregate: finalAggregate, execution });
|
|
601
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type WorkPlan, type WorkPlanEvaluation, type WorkPlanNode, type WorkPlanRefusal, type WorkPlanResource } from '../collab/work-plan.js';
|
|
2
|
+
export declare const COLLAB_ROLE_PLAN_SCHEMA = "pappra/collab-role-plan/1";
|
|
3
|
+
export declare const COLLAB_ROLE_PLAN_AGGREGATION_SCHEMA = "pappra/collab-role-plan-aggregation/1";
|
|
4
|
+
export type RolePlanRole = 'planner' | 'writer' | 'reviewer';
|
|
5
|
+
export interface RolePlanParticipant {
|
|
6
|
+
readonly participant_id: string;
|
|
7
|
+
readonly role: RolePlanRole;
|
|
8
|
+
readonly assignee: WorkPlanNode['assignee'];
|
|
9
|
+
}
|
|
10
|
+
/** All assignment pins and resource scopes are caller supplied; none are inferred. */
|
|
11
|
+
export interface RolePlanAssignment {
|
|
12
|
+
readonly node_id: string;
|
|
13
|
+
readonly role: RolePlanRole;
|
|
14
|
+
readonly assignment_id: string;
|
|
15
|
+
readonly attempt: number;
|
|
16
|
+
readonly input_revision: string;
|
|
17
|
+
readonly participant_id: string;
|
|
18
|
+
readonly resources: readonly WorkPlanResource[];
|
|
19
|
+
}
|
|
20
|
+
export interface RolePlanRequest {
|
|
21
|
+
readonly schema: typeof COLLAB_ROLE_PLAN_SCHEMA;
|
|
22
|
+
readonly plan_id: string;
|
|
23
|
+
readonly task_id: string;
|
|
24
|
+
readonly owner_epoch: number;
|
|
25
|
+
readonly participants: readonly RolePlanParticipant[];
|
|
26
|
+
readonly assignments: readonly RolePlanAssignment[];
|
|
27
|
+
}
|
|
28
|
+
export interface RolePlanDiagnostic {
|
|
29
|
+
readonly code: string;
|
|
30
|
+
readonly node_id: string | null;
|
|
31
|
+
readonly detail: string;
|
|
32
|
+
}
|
|
33
|
+
export interface RolePlanSuccess {
|
|
34
|
+
readonly schema: typeof COLLAB_ROLE_PLAN_SCHEMA;
|
|
35
|
+
readonly ok: true;
|
|
36
|
+
readonly plan: WorkPlan;
|
|
37
|
+
readonly roles: {
|
|
38
|
+
readonly planner_node_id?: string;
|
|
39
|
+
readonly writer_node_ids: readonly string[];
|
|
40
|
+
readonly reviewer_node_id: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface RolePlanRefusal {
|
|
44
|
+
readonly schema: typeof COLLAB_ROLE_PLAN_SCHEMA;
|
|
45
|
+
readonly ok: false;
|
|
46
|
+
readonly diagnostics: readonly RolePlanDiagnostic[];
|
|
47
|
+
}
|
|
48
|
+
export type RolePlanExpansion = RolePlanSuccess | RolePlanRefusal;
|
|
49
|
+
export type RolePlanNodeOutcome = 'accepted' | 'rejected' | 'unknown' | 'missing' | 'pending' | 'blocked';
|
|
50
|
+
export interface RolePlanNodeAggregation {
|
|
51
|
+
readonly node_id: string;
|
|
52
|
+
readonly role: RolePlanRole;
|
|
53
|
+
readonly outcome: RolePlanNodeOutcome;
|
|
54
|
+
/** The status observed in the existing evaluation, retained even when unknown. */
|
|
55
|
+
readonly work_plan_status: string | null;
|
|
56
|
+
readonly blockers: readonly string[];
|
|
57
|
+
}
|
|
58
|
+
export interface RolePlanAggregation {
|
|
59
|
+
readonly schema: typeof COLLAB_ROLE_PLAN_AGGREGATION_SCHEMA;
|
|
60
|
+
readonly plan_id: string;
|
|
61
|
+
readonly outcome: RolePlanNodeOutcome;
|
|
62
|
+
readonly nodes: readonly RolePlanNodeAggregation[];
|
|
63
|
+
readonly accepted_nodes: readonly string[];
|
|
64
|
+
readonly rejected_nodes: readonly string[];
|
|
65
|
+
readonly unknown_nodes: readonly string[];
|
|
66
|
+
readonly missing_nodes: readonly string[];
|
|
67
|
+
readonly pending_nodes: readonly string[];
|
|
68
|
+
readonly blocked_nodes: readonly string[];
|
|
69
|
+
readonly diagnostics: readonly RolePlanDiagnostic[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Expands an explicit role request into the existing dependency-aware WorkPlan.
|
|
73
|
+
* An optional planner precedes independent writers. The reviewer depends on every writer, so the existing
|
|
74
|
+
* evaluator—not this planner—requires each predecessor's current coordinator acceptance.
|
|
75
|
+
*/
|
|
76
|
+
export declare function expandRolePlan(input: RolePlanRequest): RolePlanExpansion;
|
|
77
|
+
/**
|
|
78
|
+
* Projects the existing read-only WorkPlan evaluation. A role-plan succeeds only when every
|
|
79
|
+
* planned node is already accepted in that evaluation; delivery observations are not inputs.
|
|
80
|
+
*/
|
|
81
|
+
export declare function aggregateRolePlan(expansion: RolePlanSuccess, evaluation: WorkPlanEvaluation | WorkPlanRefusal): RolePlanAggregation;
|